The Intel Mac Problem: When Your Git Bindings Don't Ship for x86_64
We uncovered a blocking issue with libgit2dart on Intel-based macOS machines that prevents Rope Notes from launching on that architecture. Here's what went wrong, why it's hard to fix, and what we're doing about it.
A few weeks ago, a user reported that Rope Notes crashes immediately on their Intel-based MacBook Pro. After digging through symbol tables and FFI boundaries, we traced the failure to a single root cause: our libgit2dart dependency ships precompiled binaries, and the Intel macOS binary is broken.
Here is the full story of the bug and how we plan to resolve it.
The Symptom: A Hard Crash at Startup
On Intel macOS (x86_64), Rope Notes fails during initialization of the Git integration layer. The crash is not a graceful degradation—it is a hard segfault inside the Dart FFI boundary when libgit2dart attempts to load its native libgit2 binary.
Fatal error: Invalid argument (via dart:ffi)
Caused by: dlopen failed: no suitable image found
The dynamic linker (dlopen) scans the bundled libgit2.dylib and finds an architecture mismatch. The binary is either missing the x86_64 slice entirely or was compiled against an incompatible macOS SDK version.
Root Cause: Precompiled Binaries and the Apple Silicon Transition
libgit2dart is a Dart FFI wrapper around libgit2, the C library that powers Git programmatic access. Like many Dart ecosystem packages, it ships precompiled native binaries for each target platform to avoid requiring users to have a C toolchain installed.
The problem is that the precompiled binary for macOS was compiled only for Apple Silicon (arm64) on a recent macOS SDK that dropped x86_64 backward compatibility. On an Intel Mac, dlopen finds no compatible slice and fails.
Specifically, the binary was built on macOS 15+ (Sequoia) targeting arm64 exclusively, without passing -arch x86_64 to the compiler. On Apple Silicon Macs running under Rosetta 2, the arm64 binary loads fine. On native Intel hardware, there is no Rosetta to translate—the CPU literally cannot execute the instructions.
Binary Slice Inspection (libgit2.dylib):
arm64 ✅ Apple Silicon (M1–M4)
x86_64 ❌ Missing — Intel Macs crash on load
Why It Took Us By Surprise
We develop primarily on Apple Silicon hardware, and our CI runners for macOS are also Apple Silicon. The Intel Mac use case passed silently through our test matrix because:
- No Intel macOS CI runner: GitHub Actions macOS runners are now all Apple Silicon by default. Running an Intel-specific workflow requires explicitly requesting a deprecated runner image.
- Rosetta masking: When testing on Apple Silicon, Rosetta 2 emulates
x86_64transparently. Butlibgit2dart'sarm64binary loads natively on Apple Silicon, so the missing Intel slice is never exercised. - Cross-architecture build testing is opt-in: The Dart FFI
dart:ffiloader does not validate that a binary supports the host architecture untildlopenis called at runtime.
The Fix: Fork, Vendor, Upstream
The obvious fix—rebuild the macOS binary with -arch x86_64 -arch arm64 to produce a universal (fat) binary. But waiting for an upstream maintainer to prioritize this wasn't an option. We are a local-first desktop application with paying users on Intel hardware. So we are forking libgit2dart, vendoring it directly into our build tree, and producing the universal binary ourselves.
The fork gives us full control over the build matrix:
- Universal binary compilation: We produce a single
libgit2.dylibwith botharm64andx86_64slices vialipo, compiled against a macOS SDK that maintains Intel backward compatibility. - Vendored bindings: The Dart FFI bindings live in our repository, pinned to a known-good
libgit2version. No surprise breakage whenpub.devpublishes a new binary that drops an architecture. - Upstream contribution: We will submit the universal binary build configuration and CI workflow back to the
libgit2dartproject. Everyone benefits from the fix—we just aren't blocked by the release cycle.
The Timeline
We are actively working on the fork now. Here is the status:
- Apple Silicon users: Unaffected. Rope Notes runs normally.
- Intel macOS users: Rope Notes crashes on launch. The Git integration initializes at startup, so the broken native binary takes down the entire application before any UI is rendered.
- Expected resolution: We estimate 1–2 weeks to land the vendored fork, validate the universal binary across both architectures, and ship a patch release. The upstream PR will follow once our build pipeline is proven.
What This Says About the Native Ecosystem
This bug is a microcosm of a broader challenge in the Dart and Flutter native ecosystem. As the industry rapidly transitions to ARM64, the x86_64 legacy architecture is increasingly treated as an afterthought in binary distribution pipelines. Precompiled native packages—whether for libgit2, SQLite, or audio codecs—routinely ship single-architecture binaries because CI runners and developer machines have all migrated.
For a local-first application like Rope Notes that runs on the user's hardware rather than in a browser sandbox, architecture compatibility is not optional. We are evaluating long-term changes to our native dependency strategy:
- Verified universal binaries: All native dependencies should be validated to contain slices for every architecture we support before they enter our build pipeline.
- CI matrix redundancy: Adding an explicit Intel macOS runner (even a deprecated one) to catch regressions like this before they reach users.
- Graceful degradation: Git integration should degrade to a read-only informational state rather than crash the entire application when the native library fails to load.
In the Meantime
If you are on an Intel Mac, Rope Notes will not open at all until this fix ships. We will post an update here as soon as the vendored fork is ready.
This is the nature of building on the cutting edge of the native cross-platform ecosystem. The tooling is improving rapidly, but transitions like arm64 leave cracks that take time to seal. We are committed to making sure every architecture we claim to support actually works—not just in CI, but on your desk. We are not waiting for someone else to fix it.