D
P
0
← Semua artikel Read in English

Next.js & React di Produksi

`libatomic.so.1: cannot open shared object file` di `pnpm -v`: Pin `packageManager` ke pnpm 11 Malah Membekukan Deploy

· · 7 menit baca
`libatomic.so.1: cannot open shared object file` di `pnpm -v`: Pin `packageManager` ke pnpm 11 Malah Membekukan Deploy

Pesan commit yang membawa pin pnpm ke branch main memuat kalimat yang terdengar sangat beres:

Closes the moving target that killed production for eight days in August. The
pin lives in package.json and is self-enforcing, so the build image will fetch
and run exactly this version without any change to the CodeBuild config.

Kata fetch di situ benar. Kata run tidak, begitu juga janji bahwa konfigurasi CodeBuild tidak perlu disentuh. Commit itu bertanggal 31 Agustus 2026, dan di tanggal yang sama empat commit sudah ter-push, semua ref git cocok, sementara produksi diam-diam terus menyajikan bundle lama karena build-nya gagal.

Pemadaman delapan hari yang disebut pesan itu adalah ERR_PNPM_IGNORED_BUILDS, dan mekanismenya sudah saya tulis terpisah. Akarnya cukup diringkas: package.json waktu itu tidak menetapkan packageManager, jadi image build memasang pnpm versi apa pun yang sedang terbaru, dan package manager yang tidak di-pin membuat build jadi sasaran bergerak. Pin adalah obatnya. Tulisan ini tentang efek samping obat itu.

Kenapa pin-nya ditaruh di repo

Pin itu cuma satu baris di package.json, yaitu "packageManager": "pnpm@11.21.0". Rancangan pertamanya berupa permintaan ke DevOps klien, dan rancangan itu saya tolak. Versi pnpm yang dibutuhkan proyek adalah sifat lockfile dan pnpm-workspace.yaml, bukan sifat mesin yang menjalankan install, jadi pin tempatnya di repo, bukan di buildspec. Hitungan saya waktu itu, meminta DevOps klien hanya akan menambah ketergantungan dan penundaan untuk sesuatu yang bisa dibereskan permanen oleh satu commit satu baris.

Hitungan itu bersandar pada satu perilaku pnpm. Menurut dokumentasinya, managePackageManagerVersions bernilai bawaan true sejak pnpm 10, dan pnpm 11 mempertahankan perilaku itu di bawah pmOnFail. Jadi pnpm di image build akan membaca packageManager, melihat versinya berbeda, lalu mengunduh sendiri versi yang diminta.

Versi 11.21.0 dipilih karena di versi itulah perubahan ini diverifikasi: install dengan frozen lockfile, typecheck, 300 tes, dan build produksi penuh tanpa perubahan lockfile. Pelajaran yang saya catat waktu itu terdengar rapi: periksa dulu apakah repo bisa menegakkan sesuatu sendiri sebelum menyerahkannya ke infra. Masalahnya saya tandai tertutup pada 31 Agustus, dengan commit pin itu tercatat sebagai commit produksi.

Gagal di pnpm -v, sebelum satu baris pun dikompilasi

Commit pin itu tidak pernah sampai ke produksi, begitu pula tiga commit yang di-push sesudahnya. CodeBuild gagal di fase INSTALL, saat menjalankan pnpm -v:

npm install -g pnpm          -> pnpm 10.x installed
pnpm -v
/root/.local/share/pnpm/package-manager-store/v11/links/@pnpm/exe/11.21.0/.../pnpm:
  error while loading shared libraries: libatomic.so.1: cannot open shared object file
exit status 127
Phase complete: INSTALL State: FAILED

Commit pin ditambah tiga commit sesudahnya berarti empat commit yang tampak terkirim, sementara produksi berjam-jam tertahan di commit induk langsung dari commit pin itu. Tidak ada sinyal apa pun yang menandakan build-nya gagal, dan komentar di kode proyek mencatat bahwa diam seperti itulah yang menghabiskan sejam pada 31 Agustus.

Bedanya dengan build Turbopack yang gagal diam-diam yang pernah saya tulis, build di sini mati sebelum satu baris pun dikompilasi, dan yang tidak sanggup bukan kodenya, melainkan mesin yang harus menjalankan versi pnpm pilihan repo.

Unduhannya berhasil, binernya tidak bisa dimuat

Yang membekukan deploy adalah persis mekanisme yang mau dimanfaatkan pin. Baris pertama log menunjukkan image memasang pnpm 10.x lewat npm install -g pnpm. pnpm 10 itu membaca packageManager, melihat versi yang berbeda, lalu mengunduh sendiri 11.21.0. Untuk target v11, yang diunduh bukan paket JS biasa, melainkan @pnpm/exe, biner terkompilasi yang berdiri sendiri, dan biner itulah yang terlihat di path package-manager-store/v11 pada log tadi.

Biner itu ditautkan secara dinamis ke libatomic.so.1, pustaka yang tidak dibawa image Amazon Linux tempat build berjalan. Baris yum install di image itu hanya berisi gcc gcc-c++ make git. Jadi unduhannya berhasil, lalu binernya tidak bisa dieksekusi. Jalur ini sebenarnya sudah dikenal rapuh: catatan rilis pnpm 10.23 sendiri menyebut self-update sengaja berhenti memakai @pnpm/exe untuk v11 ke atas karena masalah kompatibilitas, dan mem-pin v11 dari runtime pnpm v10 adalah persis jalur yang mengambil build executable itu.

Klaim bahwa pin menegakkan dirinya sendiri tanpa perubahan infra terbantah di sini. Mengunduh versi yang di-pin memang tidak butuh infra, tapi menjalankannya butuh. Klaim itu dulu saya cek ke dokumentasi pnpm, yang menjelaskan proses unduh tetapi tidak menyebut apa yang dibutuhkan artefak hasil unduhan untuk berjalan, dan dua hal itu tidak saya pisahkan. Hasilnya, pin yang ditambahkan untuk menutup pemadaman akibat package manager gagal saat install justru membekukan deploy dengan cara gagal saat install juga, dengan gejala yang dari luar identik.

Syarat Node yang ikut terbawa pin

libatomic ternyata baru separuhnya. Setelah membereskan pustaka itu, DevOps klien melaporkan bahwa image yang selama ini memakai Node 20 harus mereka naikkan ke 22, tanpa tahu persis apa yang berubah, lalu beberapa paket patah dan perbaikannya makan waktu. Jawabannya tertulis di metadata pnpm sendiri:

npm view pnpm@11.21.0 engines   ->   { node: '>=22.13' }

Menurut laporan itu image build menjalankan Node 20, sementara pnpm 11.21.0 meminta Node 22.13 ke atas, jadi pin satu baris itu ikut menuntut kenaikan versi mayor Node. Di mesin saya syarat itu tidak pernah muncul, karena mesin itu sudah di Node v22.23.2, dan itulah sebabnya build lokal lulus. Celahnya ada di package.json, yang waktu itu tidak punya field engines. Kalau engines.node sudah ada, syarat tersebut akan muncul sebagai deklarasi yang bisa dibaca, bukan sebagai build yang gagal.

Siapa yang membayar satu baris itu

Perbaikannya tidak datang dari repo. Menurut laporan DevOps klien, libatomic ditambahkan ke daftar paket yang dipasang image build lalu pipeline dijalankan ulang, dan kenaikan Node berikut paket yang patah juga dibereskan di sisi mereka. Dirangkum, pin satu baris itu berharga satu pustaka sistem, satu kenaikan versi mayor Node, dan paket yang patah akibat kenaikan itu, atau kira-kira dua jam waktu DevOps klien ditambah sekitar sembilan puluh menit produksi menyajikan bundle yang rusak. Ketergantungan ke DevOps klien yang mau dihindari tetap datang, hanya saja sesudah deploy beku.

Image build tetap lapisan yang tepat untuk perbaikan ini, karena package.json tetap jadi satu-satunya sumber angka versi, dan di bagian itu penalaran awalnya benar. Jalan lainnya, mem-pin versi sekali lagi di buildspec lewat npm install -g pnpm@11.21.0, juga berhasil karena memasang paket JS biasa dan melewati unduhan mandiri (syarat versi Node-nya tetap berlaku), tapi angka versinya jadi tertulis di dua tempat yang bisa saling menyimpang.

Yang diubah di repo, dan yang belum saya pastikan

Dua tindak lanjut di sisi repo masuk pada 31 Agustus. Yang pertama koreksi komentar kepala pnpm-workspace.yaml, yang sebelumnya masih menyatakan pnpm mengunduh dan menjalankan versi itu apa pun isi image build. Sekarang komentar itu memuat bagian ini:

# THE PIN IS NOT FREE OF THE BUILD IMAGE, which is what the first version of
# this comment claimed. Downloading the pinned version needs nothing. Running
# it does.

Beberapa baris di bawahnya, komentar yang sama mencatat bahwa perbaikannya adalah menambah libatomic ke paket image build, dan itulah hal pertama yang harus dicek kalau masalahnya muncul lagi di image baru. Yang kedua deklarasi Node di package.json, tepat di bawah pin-nya:

  "packageManager": "pnpm@11.21.0",
  "engines": {
    "node": ">=22.13"
  },

Menurut pesan commit-nya, deklarasi ini tidak menegakkan apa pun dengan sendirinya. Gunanya supaya orang berikutnya yang memilih versi pnpm bisa melihat runtime apa yang ikut terseret, bukan mengetahuinya dari build yang gagal.

Soal perbaikan di image, buktinya tidak langsung, karena catatan saya tidak memuat langkah verifikasi bahwa deploy sesudahnya mendarat. Petunjuknya ada di riwayat git: sebuah route yang menampilkan commit yang ter-deploy ditambahkan pada 31 Agustus, dan pesan commit lain bertanggal sama mencatat route itu sudah tayang di produksi, meski langsung melaporkan sha unknown, urusan yang terpisah. Artinya setidaknya satu build sesudah perbaikan memang mendarat. Pesan commit bertanggal 28 September juga menyebut pipeline sudah berjalan di Node 22.

Sebutkan artefak yang akan berjalan

Pelajaran pertama saya, bahwa repo bisa menegakkan ini sendiri, ternyata cuma setengah benar. Memilih versi adalah keputusan repo. Membuat versi itu benar-benar berjalan adalah sifat mesin, dan repo tidak bisa menjamin apa pun tentang mesin yang belum pernah dia lihat. Jadi sebelum menyebut sebuah perubahan tidak butuh infra, sebutkan dulu artefak yang akan berjalan dan apa yang ia butuhkan. Ketergantungan yang tidak terlihat tetap ketergantungan.

Untuk pin package manager, itu berarti dua pertanyaan sebelum commit: artefak apa yang akan diunduh, dan apa syarat engines milik package manager itu sendiri, karena pin ikut membawanya. Untuk yang kedua, jalankan npm view <pkg>@<version> engines sebelum mem-pin versi mayor dan bandingkan dengan Node yang benar-benar dijalankan image build, sebab lulus di lokal tidak membuktikan apa-apa kalau mesin lokal beberapa versi lebih maju.

Kata fetch dan run di pesan commit tadi ternyata dua janji yang berbeda, dan repo hanya sanggup memegang yang pertama.