ci(release): build artifacts on macos-latest to fix darwin-x64 signing#11415
Conversation
Cross-signing darwin Mach-O binaries on Linux with the saurik fork of ldid produces an ad-hoc signature whose page hashes don't match the post-postject layout for Node.js 25's chained fixups, leaving fixups unapplied at load and crashing the binary in __cxx_global_var_init (EXC_BAD_ACCESS at 0x3 — the unprocessed chain-entry tag). Running the release on macos-latest lets pack-app's adHocSignMacBinary use native codesign, which understands chained fixups. Drops the entire ldid build step.
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThe release workflow's runner environment switches from Ubuntu to macOS with inline comments explaining Darwin artifact signing. The Linux-specific Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Review rate limit: 9/10 reviews remaining, refill in 6 minutes. Comment |
There was a problem hiding this comment.
Pull request overview
Updates the GitHub Release workflow to run on macOS so the darwin artifacts are re-signed using native codesign, addressing the reported darwin-x64 startup crash caused by invalid ad-hoc signatures produced during Linux cross-signing.
Changes:
- Switch the release job runner from
ubuntu-latesttomacos-latest. - Remove the Linux-only
ldidinstallation/build step and document the signing rationale inline.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
#11415) * ci(release): build artifacts on macos-latest to fix darwin-x64 signing Cross-signing darwin Mach-O binaries on Linux with the saurik fork of ldid produces an ad-hoc signature whose page hashes don't match the post-postject layout for Node.js 25's chained fixups, leaving fixups unapplied at load and crashing the binary in __cxx_global_var_init (EXC_BAD_ACCESS at 0x3 — the unprocessed chain-entry tag). Running the release on macos-latest lets pack-app's adHocSignMacBinary use native codesign, which understands chained fixups. Drops the entire ldid build step. * ci(release): document why release runs on macos-latest
## Summary Closes #11423. `pnpm-darwin-x64.tar.gz` and `@pnpm/macos-x64` are removed because the binaries they contain segfault at startup on Intel Macs and the underlying bug is upstream and unfixed. ## Why this isn't a fix in code The crash happens in `__cxx_global_var_init` with `EXC_BAD_ACCESS (code=1, address=0x3)` — the unprocessed-chain-entry tag — in dyld's chained-fixup processing. PR #11415's hypothesis was that `ldid`'s page hashes were the cause, but switching to native `codesign` in #11415 didn't fix it: the upstream minimal repro in [nodejs/node#62893](nodejs/node#62893) is `node --build-sea` + `codesign --sign -` + run, with no pnpm and no `ldid`, and it still crashes. The corruption is in LIEF's Mach-O surgery during `--build-sea` for x64 — chained-fixup chain entries get rewritten incorrectly when the SEA segment is inserted, and re-signing produces a valid signature over the broken bytes. The Node.js team is not going to fix this: - [nodejs/node#60250](nodejs/node#60250) (merged) — *"It's unlikely that anyone would invest in fixing them on x64 macOS in the near future, now that x64 macOS is being phased out."* They skipped the SEA tests on x64 macOS rather than chase the bug. - [nodejs/node#59553](nodejs/node#59553) (open) — long-running test failures on macOS x64 with the same root cause (sometimes surfacing as `unsupported thread-local, larger than 4GB`). `@yao-pkg/pkg` works around it by appending the JS payload to the file tail and using a custom-patched Node binary that reads from the tail at startup; this avoids Mach-O surgery entirely. We can't reuse pack-app for that because vanilla Node from nodejs.org doesn't read tail-appended payloads — only pkg-fetch's patched binaries do — so adopting that path would mean re-implementing pkg-fetch for one target. For now we're dropping the broken artifact rather than introducing a second build mechanism. ## Changes - **`pnpm/artifacts/exe/package.json`** — remove `@pnpm/macos-x64` from `optionalDependencies`; remove `darwin-x64` from `pnpm.app.targets`. - **`.meta-updater/src/index.ts`** — remove `@pnpm/macos-x64` from the enforced `optionalDependencies` list (otherwise `meta-updater` would put it back). - **`pnpm/artifacts/exe/scripts/build-artifacts.ts`** — drop `darwin-x64` from `narrowTargets` so dev-local builds match the published matrix; comment explains why. - **`__utils__/scripts/src/copy-artifacts.ts`** — stop creating `pnpm-darwin-x64.tar.gz` so the GitHub release page no longer ships it. - **`pnpm/artifacts/darwin-x64/`** — deleted (was the workspace source for `@pnpm/macos-x64`). - **`pnpm/artifacts/exe/setup.js`** — wraps the `import.meta.resolve('${pkgName}/package.json')` lookup in `try`/`catch`. On Intel Mac specifically, prints a clear message pointing at this issue, the upstream Node.js issue, and the two workarounds (`npm install -g pnpm` to use the system Node.js, or stay on pnpm 10.x). Other unsupported hosts get a generic message in the same shape. Exits non-zero so the install fails loudly instead of silently leaving a broken `pnpm`. - **`pnpm-lock.yaml`** — regenerated. - **`.changeset/drop-darwin-x64-broken-sea.md`** — patch bumps for `@pnpm/exe` and `pnpm` with user-facing explanation and pointers. Docs side already lists this limitation under `pack-app` Known limitations: pnpm/pnpm.io@36d962f6 / pnpm/pnpm.io@91f45632. ## Compat - Intel Mac users on existing `@pnpm/exe` (≤ 11.0.4) keep working with the (broken) old binary they already have. - `pnpm self-update` from an Intel Mac on an older `@pnpm/exe` will hit the new `setup.js` error path with a clear pointer to the workarounds. - New Intel Mac installs via `npm install -g @pnpm/exe` will fail loudly with the same pointer. - Install via `npm install -g pnpm` (the JS-only package, uses system Node) is unaffected and remains the recommended path. - The `install.sh` from `get.pnpm.io` will fail with a 404 on the missing `pnpm-darwin-x64.tar.gz`. That's a separate repo and a follow-up — happy to do that as a second PR.
## Summary Closes #11423. `pnpm-darwin-x64.tar.gz` and `@pnpm/macos-x64` are removed because the binaries they contain segfault at startup on Intel Macs and the underlying bug is upstream and unfixed. ## Why this isn't a fix in code The crash happens in `__cxx_global_var_init` with `EXC_BAD_ACCESS (code=1, address=0x3)` — the unprocessed-chain-entry tag — in dyld's chained-fixup processing. PR #11415's hypothesis was that `ldid`'s page hashes were the cause, but switching to native `codesign` in #11415 didn't fix it: the upstream minimal repro in [nodejs/node#62893](nodejs/node#62893) is `node --build-sea` + `codesign --sign -` + run, with no pnpm and no `ldid`, and it still crashes. The corruption is in LIEF's Mach-O surgery during `--build-sea` for x64 — chained-fixup chain entries get rewritten incorrectly when the SEA segment is inserted, and re-signing produces a valid signature over the broken bytes. The Node.js team is not going to fix this: - [nodejs/node#60250](nodejs/node#60250) (merged) — *"It's unlikely that anyone would invest in fixing them on x64 macOS in the near future, now that x64 macOS is being phased out."* They skipped the SEA tests on x64 macOS rather than chase the bug. - [nodejs/node#59553](nodejs/node#59553) (open) — long-running test failures on macOS x64 with the same root cause (sometimes surfacing as `unsupported thread-local, larger than 4GB`). `@yao-pkg/pkg` works around it by appending the JS payload to the file tail and using a custom-patched Node binary that reads from the tail at startup; this avoids Mach-O surgery entirely. We can't reuse pack-app for that because vanilla Node from nodejs.org doesn't read tail-appended payloads — only pkg-fetch's patched binaries do — so adopting that path would mean re-implementing pkg-fetch for one target. For now we're dropping the broken artifact rather than introducing a second build mechanism. ## Changes - **`pnpm/artifacts/exe/package.json`** — remove `@pnpm/macos-x64` from `optionalDependencies`; remove `darwin-x64` from `pnpm.app.targets`. - **`.meta-updater/src/index.ts`** — remove `@pnpm/macos-x64` from the enforced `optionalDependencies` list (otherwise `meta-updater` would put it back). - **`pnpm/artifacts/exe/scripts/build-artifacts.ts`** — drop `darwin-x64` from `narrowTargets` so dev-local builds match the published matrix; comment explains why. - **`__utils__/scripts/src/copy-artifacts.ts`** — stop creating `pnpm-darwin-x64.tar.gz` so the GitHub release page no longer ships it. - **`pnpm/artifacts/darwin-x64/`** — deleted (was the workspace source for `@pnpm/macos-x64`). - **`pnpm/artifacts/exe/setup.js`** — wraps the `import.meta.resolve('${pkgName}/package.json')` lookup in `try`/`catch`. On Intel Mac specifically, prints a clear message pointing at this issue, the upstream Node.js issue, and the two workarounds (`npm install -g pnpm` to use the system Node.js, or stay on pnpm 10.x). Other unsupported hosts get a generic message in the same shape. Exits non-zero so the install fails loudly instead of silently leaving a broken `pnpm`. - **`pnpm-lock.yaml`** — regenerated. - **`.changeset/drop-darwin-x64-broken-sea.md`** — patch bumps for `@pnpm/exe` and `pnpm` with user-facing explanation and pointers. Docs side already lists this limitation under `pack-app` Known limitations: pnpm/pnpm.io@36d962f6 / pnpm/pnpm.io@91f45632. ## Compat - Intel Mac users on existing `@pnpm/exe` (≤ 11.0.4) keep working with the (broken) old binary they already have. - `pnpm self-update` from an Intel Mac on an older `@pnpm/exe` will hit the new `setup.js` error path with a clear pointer to the workarounds. - New Intel Mac installs via `npm install -g @pnpm/exe` will fail loudly with the same pointer. - Install via `npm install -g pnpm` (the JS-only package, uses system Node) is unaffected and remains the recommended path. - The `install.sh` from `get.pnpm.io` will fail with a 404 on the missing `pnpm-darwin-x64.tar.gz`. That's a separate repo and a follow-up — happy to do that as a second PR.
Summary
Fixes EXC_BAD_ACCESS at startup of the darwin-x64 release binary (reported against v11.0.2/v11.0.3 on Intel Macs).
The root cause is in the cross-signing step, not pnpm code. The release flow:
pnpm pack-appdownloads the official darwin-x64 Node.js 25.9.0 and uses postject to splice the SEA blob into a Mach-O segment.ldid -Sfrom the (saurik fork, pinned toc2f8abf01…).Node.js 25 binaries use
LC_DYLD_CHAINED_FIXUPS. When postject mutates the binary and the saurik fork ofldidrecomputes the ad-hoc signature, the resultingLC_CODE_SIGNATUREpage hashes don't match what the kernel sees on macOS 11+. dyld validates pages, skips fixup application on the offending pages, and a C++ global initializer reads a raw chained-fixup chain entry as a pointer — landing onaddress=0x3, which is exactly the low-bit "next-offset" pattern of an unprocessed chain entry.A reporter's lldb trace makes this unambiguous:
Fix
Move the entire release job onto
macos-latest.pack-app'sadHocSignMacBinary(releasing/commands/src/pack-app/packApp.ts:555-557) already uses nativecodesign --sign -when running on darwin, which knows how to compute correct page hashes against post-postject Mach-O including chained-fixup data. No application code changes are required.Side-effects:
Install ldidstep (apt-get + GitLab clone + g++ build) is removed.macos-latestis Apple Silicon, sobuildFullMatrixinbuild-artifacts.ts:16evaluates true — same 8-target matrix as today.verify-binary.mjs's execution check shifts fromlinux-x64todarwin-arm64. Existence verification still runs for all targets.Test plan
vX.Y.Z-rc.Nto exercise the workflow end-to-end onmacos-latest.pnpm-darwin-x64.tar.gzand confirm./pnpm --versionruns without segfaulting on an Intel Mac.codesign -dv --verbose=4 ./pnpmshows a valid ad-hoc signature with hashes matching the file.pnpm-linux-x64.tar.gz,pnpm-linux-arm64.tar.gz,pnpm-linux-x64-musl.tar.gz,pnpm-linux-arm64-musl.tar.gz,pnpm-darwin-arm64.tar.gz,pnpm-win32-x64.zip,pnpm-win32-arm64.zipare all produced.Summary by CodeRabbit