feat: --ignore-node-options to keep the embedded V8 code cache valid#31
Merged
Conversation
A Node SEA with an embedded V8 code cache (`useCodeCache`) emits `Warning: Code cache data rejected` and falls back to recompiling whenever the user has V8 flags in `NODE_OPTIONS` (e.g. `--max-old-space-size`, common for Claude Code). Those flags change V8's `FlagList::Hash()` at runtime, so it no longer matches the build-time default the cache was generated with. `--ignore-node-options` makes the produced binary ignore `NODE_OPTIONS`, the equivalent of building Node with `./configure --without-node-options` but via an in-place binary patch (like the hole-punch step): it renames the single `.rodata` `NODE_OPTIONS` C-string that the C++ bootstrap feeds to `SafeGetenv()` to a same-length inert name, so `getenv()` returns null and no flags are applied. The runtime flag-hash then matches the build-time default and the embedded code cache is accepted. `process.env` is untouched, so `process.env.NODE_OPTIONS` is still visible to the app and still inherited by any child process it spawns. The target is selected in a format-agnostic way (Mach-O/ELF/PE): the `NODE_OPTIONS` occurrence that is null-bounded and surrounded by other C-strings. This excludes the JS/help/error copies and, critically, the copies inside the checksummed V8 startup snapshot (which must not be modified). Verified to patch exactly one offset on node-v22.14.0 darwin-arm64, linux-x64, linux-arm64 and win-x64. Throws if no candidate is found so a future Node layout change fails the build loudly.
2 tasks
BYK
added a commit
to BYK/loreai
that referenced
this pull request
Jun 11, 2026
…de cache valid (#710) ## Problem The standalone `lore` binary emits `Warning: Code cache data rejected` at startup — and silently recompiles its main script — for any user who has V8 flags in `NODE_OPTIONS` (e.g. `NODE_OPTIONS=--max-old-space-size=8192`, a very common Claude Code setup). The binary is a Node SEA with an embedded V8 code cache generated on CI with default flags. V8 flags from `NODE_OPTIONS` change `FlagList::Hash()` at runtime, so the runtime hash no longer matches the build-time default and V8 rejects the cache. Diagnosed from a real report (Onur): same binary **accepted** on a machine with no `NODE_OPTIONS`, **rejected** on one with `NODE_OPTIONS` V8 flags; `env -u NODE_OPTIONS lore` made the warning disappear. (It was not the chip / macOS version — both were Apple-silicon, same macOS.) ## Fix - Bump `fossilize` to `^0.10.0`. - Pass `ignoreNodeOptions: true` to `fossilize()` in `runFossilize()`. fossilize then patches the binary to ignore `NODE_OPTIONS` (equivalent to building Node with `--without-node-options`): it renames the `.rodata` `NODE_OPTIONS` lookup constant so `getenv()` returns null and no V8 flags are applied. The runtime flag-hash matches the build-time default → the embedded code cache is **accepted** (kept; not disabled). `process.env` is untouched, so `process.env.NODE_OPTIONS` stays set and the agent lore launches (e.g. claude) still inherits the user's flags. Upstream: BYK/fossilize#31 (released in fossilize 0.10.0). ## Verification - Reproduced + fixed on a real linux-x64 code-cache SEA; fossilize CI proves acceptance under `NODE_OPTIONS` on macOS/Linux/Windows. - Full typecheck + Biome clean.
BYK
added a commit
to getsentry/cli
that referenced
this pull request
Jun 11, 2026
… cache valid (#1092) ## Problem The standalone `sentry` binary is a Node SEA with an embedded V8 code cache. When a user has V8 flags in `NODE_OPTIONS` (e.g. `NODE_OPTIONS=--max-old-space-size=8192`, common in JS toolchains), those flags change V8's `FlagList::Hash()` at runtime, so V8 rejects the build-time code cache and prints `Warning: Code cache data rejected` (then recompiles). ## Fix - Bump `fossilize` `^0.8.1` → `^0.10.1`. - Pass `--ignore-node-options` to the fossilize invocation in `script/build.ts`. fossilize patches the binary to ignore `NODE_OPTIONS` (equivalent to building Node with `--without-node-options`): it renames the `.rodata` `NODE_OPTIONS` lookup constant so `getenv()` returns null and no V8 flags are applied. The runtime flag-hash then matches the build-time default and the embedded code cache is accepted (kept; not disabled). `process.env` is untouched, so `process.env.NODE_OPTIONS` stays set and any child process still inherits the user's flags. fossilize 0.10.x details: BYK/fossilize#31 (the feature) and BYK/fossilize#33 (NUL-termination selector fix for win-x64 / node 24 — relevant here since `NODE_VERSION` is `lts` = 24.x). ## Notes - Only host-platform (linux-x64, built on ubuntu) currently embeds a code cache; the patch is applied to all targets and is a no-op effect for cross-compiled ones (just renames the constant), so it's safe everywhere. - Typecheck + Biome lint clean; lockfile change is fossilize-only.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
A Node SEA built with an embedded V8 code cache (
useCodeCache) emitsWarning: Code cache data rejectedat startup — and silently falls back torecompiling the main script — whenever the user has V8 flags in
NODE_OPTIONS(e.g.
NODE_OPTIONS=--max-old-space-size=8192, a very common Claude Codesetup). Those flags change V8's
FlagList::Hash()at runtime, so it no longermatches the build-time default the cache was generated against, and V8 rejects
the cache.
This was diagnosed from a real report: the same binary was accepted on a
machine with no
NODE_OPTIONSand rejected on one with V8 flags inNODE_OPTIONS;env -u NODE_OPTIONS <binary>made the warning disappear.Fix
New opt-in
--ignore-node-optionsflag. It makes the produced binary ignoreNODE_OPTIONS— the equivalent of building Node with./configure --without-node-options, achieved via an in-place binary patch(same model as the existing hole-punch step):
.rodataNODE_OPTIONSC-string that the C++ bootstrapfeeds to
SafeGetenv()to a same-length inert name (NODE_OPTIQNS), sogetenv()returns null and no V8 flags are applied fromNODE_OPTIONS.code cache is accepted (kept; no
--no-code-cachetradeoff).process.envis untouched, soprocess.env.NODE_OPTIONSis still visible tothe app and still inherited by any child process it spawns.
Applied after
strip, before code-cache generation and signing, so both thegenerated cache and the final signature cover the patched bytes.
Robust, format-agnostic target selection
A Node binary holds ~10 copies of the literal
NODE_OPTIONS(help/error text,JS bootstrap, and copies inside the checksummed V8 startup snapshot which
must not be touched). The patch targets the
.rodatalookup constant byselecting the occurrence that is null-bounded (
\0NODE_OPTIONS\0) andsurrounded on both sides by C-string bytes (NUL/printable-ASCII). This excludes
the JS/error copies and the snapshot copies (which sit next to non-printable
serialized bytes). It throws if no candidate is found so a future Node
layout change fails the build loudly instead of shipping a binary whose cache
would be rejected.
Verified to patch exactly one offset on official node-v22.14.0
darwin-arm64, linux-x64, linux-arm64 and win-x64 builds.
Verification
Code cache data rejectedon a real linux-x64 code-cache SEA withNODE_OPTIONS=--stack-trace-limit=99; after the patch the warning is gone andthe cache is still embedded.
Test --ignore-node-options keeps the code cache valid under NODE_OPTIONS) builds with the flag and asserts no rejection while runningunder
NODE_OPTIONS.process.env.NODE_OPTIONSconfirmed still present (children still inherit it).