Skip to content

Fix macOS x64 crash on launch: package sharp for both architectures - #89

Merged
alpha5611331 merged 5 commits into
mainfrom
verify/macos-sharp
Aug 9, 2026
Merged

Fix macOS x64 crash on launch: package sharp for both architectures#89
alpha5611331 merged 5 commits into
mainfrom
verify/macos-sharp

Conversation

@alpha5611331

Copy link
Copy Markdown
Member

Fixes #88

The bug

The x64 macOS build crashed on launch with Could not load the "sharp" module using the darwin-x64 runtime. Two independent packaging defects:

  1. The darwin-x64 binary was never installed. pnpm 10+ materialises only the host
    architecture's optional dependencies, but electron-builder --mac packages both mac
    architectures from one node_modules. On the arm64 runner, @img/sharp-darwin-x64 was
    never on disk.
  2. sharp was never unpacked from the asar. Its .node finds libvips in a sibling
    package via an @rpath, and dlopen cannot read a dylib inside an asar. This would have
    broken arm64 too - x64 just failed earlier, at the require.

The fix

  • pnpm-workspace.yaml: supportedArchitectures so every target platform's binary is
    installed, not just the runner's.
  • package.json: asarUnpack for sharp and @img, plus per-platform files negations so
    the Windows installer does not inherit ~34 MB of darwin libvips.

Each mac artifact now carries both architectures' libvips. That is unavoidable while one
node_modules feeds both targets, and it is the trade that makes the x64 build work.

Verification

Nothing in CI launched or inspected the packaged app, which is why this shipped. Added two
checks that run on real runners (verify-native-deps.yml), plus a gate on the release job:

  • test/verify-packaged-sharp.mjs - derives each artifact's architecture from its Mach-O/PE
    header, asserts the matching binary is unpacked, resolves libvips the way dyld will,
    verifies the bundle signature covers the unpacked Mach-O files, then loads sharp from the
    packaged asar and runs the real grayscale().png() operation.
  • test/smoke-packaged-launch.mjs - launches the packaged app for real. Loading sharp through
    ELECTRON_RUN_AS_NODE never starts Electron proper, so it cannot catch a failure that only
    appears once the main process boots. The uncaught-exception dialog also keeps the process
    alive, so the check requires clean output as well as survival.

Results on a real arm64 macOS runner:

darwin-x64 darwin-arm64 win32-x64
unpacked .node, correct arch ok ok ok
libvips via @rpath ok ok n/a
bundle signature covers binaries ok ok n/a
sharp loads and runs ok ok ok
app launches, no uncaught exception ok ok ok

mac/ is the exact darwin-x64 artifact that was crashing. Both gates were confirmed to fail
on a deliberately broken build before being trusted.

Also included

42832bb (@alpha5611331) fixes the release notes, which pointed every macOS user at the
-arm64.dmg - so Intel users were handed a build their machine cannot run at all. Same bug
class, so it belongs with this change.

Note for a follow-up

--disable-content-protection does not work on the packaged app: a packaged Electron binary
rejects the unrecognised option outright, so it exits before src/main/index.ts:161 reads it.
It works in dev, where args follow the script path. Pre-existing and out of scope here, but
CLAUDE.md documents it without that caveat.

🤖 Generated with Claude Code

alpha5611331 and others added 5 commits August 8, 2026 19:08
pnpm materialises only the host architecture's optional dependencies, but
electron-builder packages both mac architectures from one node_modules. The
arm64 runner therefore produced an x64 artifact with no @img/sharp-darwin-x64,
which threw "Could not load the sharp module" on first launch.

Separately, sharp was never unpacked from the asar. Its native binary finds
libvips in a sibling package through an @rpath, and dlopen cannot reach a dylib
inside an asar, so the arm64 build was broken by the same packaging gap.

Add a release gate that inspects each packaged app and loads sharp from it, plus
a verify workflow that runs the packaging on real macOS and Windows runners.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The first line of a Node module error is just the loader frame, which says
nothing about which module was missing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CI invokes the gate with a relative path, and require() reads a bare relative
specifier as a package name, so the runtime check failed on a correctly packaged
app.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Loading sharp through ELECTRON_RUN_AS_NODE proves the binary resolves but never
starts Electron, so it cannot catch a native module that only fails once the
main process boots - which is the failure users report.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@alpha5611331
alpha5611331 merged commit 65962ca into main Aug 9, 2026
3 checks passed
@mchl7852
mchl7852 deleted the verify/macos-sharp branch August 9, 2026 22:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

macOS x64 build crashes on launch: Could not load the "sharp" module

1 participant