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_64 transparently. But libgit2dart's arm64 binary loads natively on Apple Silicon, so the missing Intel slice is never exercised.
  • Cross-architecture build testing is opt-in: The Dart FFI dart:ffi loader does not validate that a binary supports the host architecture until dlopen is 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:

  1. Universal binary compilation: We produce a single libgit2.dylib with both arm64 and x86_64 slices via lipo, compiled against a macOS SDK that maintains Intel backward compatibility.
  2. Vendored bindings: The Dart FFI bindings live in our repository, pinned to a known-good libgit2 version. No surprise breakage when pub.dev publishes a new binary that drops an architecture.
  3. Upstream contribution: We will submit the universal binary build configuration and CI workflow back to the libgit2dart project. 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.