Skip to content

fix(ensure-deps): stop Bun from seeding an ABI-mismatched native cache - #1195

Closed
jgbriel-io wants to merge 1 commit into
mksglu:nextfrom
jgbriel-io:fix/bun-abi-cache-mislabel
Closed

jgbriel-io wants to merge 1 commit into
mksglu:nextfrom
jgbriel-io:fix/bun-abi-cache-mislabel

Conversation

@jgbriel-io

Copy link
Copy Markdown

Fixes the primary defect in #1193.

The bug

The Bun branch added for #543 seeds the ABI cache:

if (existsSync(nativeDir) && existsSync(binaryPath) && !existsSync(abiCachePath)) {
  copyFileSync(binaryPath, abiCachePath);
}

abiCachePath is named from process.versions.modules. Bun spoofs that value; the bytes at binaryPath were produced by whichever Node last ran npm rebuild. So the copy is named after an ABI that nothing verified it matches, and the existing comment states the assumption that turned out to be wrong:

// Bun spoofs process.versions.modules to match the Node ABI level (e.g. 137 on
// Darwin matching Node 24), so a plain file-copy produces the correct
// filename for any subsequent Node boot at the same ABI.

The filename is right for the spoofed ABI. The bytes are not.

Nothing downstream checks. The fast path swaps the cached file into the active slot and, when skipProbe is true, returns on // Trust the cached binary — skip SIGSEGV-prone probe without ever loading it — so a Node boot at the spoofed ABI installs a mismatched binding as the live one.

Observed on an Ubuntu install running context-mode 1.0.169, Node v22.23.2 (ABI 127) and Bun 1.4.2, with no ABI-147 runtime anywhere on the machine:

$ md5sum node_modules/better-sqlite3/build/Release/*.node
4b1a48f7cf589d4ebfa5722b31c80a68  better_sqlite3.abi127.node
4b1a48f7cf589d4ebfa5722b31c80a68  better_sqlite3.abi147.node
4b1a48f7cf589d4ebfa5722b31c80a68  better_sqlite3.node

abi147.node is an ABI-127 binary, written 156 ms after abi127.node by the Bun seed.

The fix

Under Bun the store runs on bun:sqlite and better-sqlite3 is never dlopened, so the ABI cache is not Bun's to write and it cannot tell which ABI the file on disk holds. The branch becomes a bare early return.

That leaves #543's actual complaint — a spurious Native addon ABI cache missing warning from /ctx-upgrade under Bun — so src/cli.ts now reports Native addons OK — not required under Bun for that case. The warning goes away without writing an unverifiable file to disk.

I considered two alternatives and rejected both:

  • Name the entry from the binary's embedded ABI. better-sqlite3's prebuilds carry no NODE_MODULE_VERSION marker I could read back (strings finds nothing), so this needs a sidecar file recording the building ABI — new on-disk state for a cosmetic warning.
  • Validate in the fast path before trusting the cache. Validation means a dlopen, which is what SIGSEGV coredumps from better-sqlite3 probe in ensure-deps.mjs on Node v24 (gap left by #228) #331 and the skipProbe early return exist to avoid.

Happy to switch to either if you prefer one.

Tests

tests/core/cli.test.ts — the Bun ABI cache seeding (#543) block asserted the old behaviour, including the string produces the CORRECT filename, so it is replaced rather than extended. Six tests now cover: no cache entry created under Bun, no abi*.node file created for any ABI, missing binary and missing nativeDir still not throwing, an existing entry left untouched, and a source-contract guard that the Bun gate performs no file write at all.

Three of the six fail against next and pass with the fix:

× under Bun, with active .node but no abi cache: leaves the cache absent
× under Bun, no abi-named file is created for any ABI
× under Bun, the branch writes nothing at all

Full suite on this branch:

npm run typecheck   # clean
npx vitest run
Test Files  213 passed (213)
     Tests  4756 passed | 44 skipped (4800)

Ubuntu, kernel 7.0.0-31-generic, Node v22.23.2, npm 10.9.8.

The Bun branch added for #543 copied the active better_sqlite3.node to
better_sqlite3.abi${process.versions.modules}.node. Bun spoofs that value,
while the bytes being copied were produced by whichever Node last ran
`npm rebuild` — so the copy is named after an ABI nothing verified it
matches.

Nothing downstream validates the name. The fast path swaps the cached
file into the active slot and, when skipProbe is true, returns without
loading it, which installs an ABI-mismatched binding as the live one. On
one Linux install this left better_sqlite3.abi127.node and
better_sqlite3.abi147.node byte-identical on a host with only Node 22
(ABI 127) present.

Under Bun the store runs on bun:sqlite and better-sqlite3 is never
dlopened, so the ABI cache is not Bun's to write. The branch is now a
bare early return, and /ctx-upgrade reports "not required under Bun"
instead of warning about a marker that does not apply — which addresses
the spurious warning #543 was opened for without writing an unverifiable
file to disk.

Refs #543, #1193
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.

1 participant