Repository navigation
Upstream pkg-fetch patches to Node.js core to eliminate custom binaries #231
Description
Goal
Eliminate the need for pkg-fetch to compile and distribute custom-patched Node.js binaries. Every Node.js version requires ~600-850 lines of patches across ~25 files — this is the single biggest maintenance burden in the project. If we upstream the right changes to Node.js core, pkg can use stock Node.js binaries.
This issue tracks each patch category, its upstream path, and progress.
Related: #204 (SEA migration strategy)
Patch Inventory
Every pkg-fetch patch applies the same 9 conceptual changes, adapted per Node.js version. Here's what each does and how to eliminate it:
1. Binary Entrypoint Injection ("BAKERY") — node_main.cc
What: Replaces main() with reorder() that reads baked-in CLI flags from a 200-byte marker string. Uses volatile + optimization barriers to prevent the compiler from stripping it.
Why pkg needs it: Injects flags like --no-warnings and sets PKG_DUMMY_ENTRYPOINT as argv[1] to redirect execution to the embedded payload.
Upstream path: Eliminated by --build-sea — SEA has its own injection mechanism via sea-config.json execArgv. No upstream PR needed.
2. Bootstrap/Prelude Loader — bootstrap/pkg.js + node.cc
What: Adds StartExecution(env, "internal/bootstrap/pkg") early in Node.js startup. The JS bootstrap reads the executable, finds PAYLOAD_POSITION/PRELUDE_POSITION markers, and executes the prelude via vm.Script.
Why pkg needs it: Runs pkg's VFS setup before any user code.
Upstream path: Eliminated by node:vfs (nodejs/node#61478). VFS provides native mounting — no need for a custom bootstrap.
3. VFS Interception (Module Loading) — cjs/loader.js + package_json_reader.js
What: Replaces native C++ bindings (internalModuleStat, readPackageJSON) with JS equivalents that route through require('fs') instead of directly hitting the OS filesystem.
Why pkg needs it: Without this, require() resolution bypasses the prelude's fs monkey-patching entirely — modules can't be loaded from the VFS.
Upstream path: Eliminated by node:vfs (nodejs/node#61478). With native VFS hooks in the module loader, no interception is needed.
4. V8 Sourceless Bytecode — 8 V8 files + vm.js + node_contextify.cc (~100 lines)
What: The most complex patch. Adds 3 new V8 APIs:
EnableCompilationForSourcelessUse()— forces eager compilation (lazy=false,predictable=true)DisableCompilationForSourcelessUse()— restores flagsFixSourcelessScript()— strips source from compiled script, leaving only bytecode
Also: prevents GC from flushing sourceless bytecode, adds parser guards for undefined source, removes code cache sanity checks (source hash, flags hash), adds Function.prototype.toString() fallback ("class {}") for sourceless classes, and adds sourceless option to vm.Script.
Why pkg needs it: Enables shipping V8 bytecode without source code for IP protection and potentially faster startup.
Upstream path: Propose as RFC — "V8 bytecode-only execution mode" behind --allow-sourceless-scripts or as a vm.Script option. Use cases: code protection for commercial apps, reduced deployment size, edge computing. This is the highest-impact upstream proposal but also the most contentious (security implications of disabling sanity checks).
5. SIGUSR1 Removal — node.cc + inspector_agent.cc
What: Removes SIGUSR1 signal handler setup and StartDebugSignalHandler() call.
Why pkg needs it: Packaged apps may use SIGUSR1 for their own purposes (log rotation, graceful reload). The default handler would unexpectedly try to start an inspector session.
Upstream path: Propose --disable-inspector configure flag or leverage SEA's existing inspector restrictions.
6. Debug Options Disabled — node_options.cc
What: Adds return; at the start of DebugOptionsParser constructor, preventing registration of --inspect* options.
Why pkg needs it: Prevents exposing embedded source/bytecode via the debugger.
Upstream path: Same as patch 5 — a --disable-inspector flag covers both.
7. Process Init Guards — pre_execution.js
What: Adds double-initialization guard (_alreadyPrepared flag), PKG_DUMMY_ENTRYPOINT check, and --require assertion.
Why pkg needs it: Prevents crashes from pkg's unusual startup sequence where prepareMainThreadExecution() is called twice.
Upstream path: Eliminated by node:vfs — with native VFS, there's no unusual startup sequence.
8. child_process.fork() Bug — child_process.js (1 line)
What: Changes spawn(...) to module.exports.spawn(...).
Why pkg needs it: Direct call to locally-scoped spawn bypasses any monkey-patching on child_process.spawn, so pkg's child process interception never fires.
Upstream path: Submit as bug fix PR to Node.js core. This is a legitimate bug — any tool that wraps child_process.spawn via exports is affected. One-line fix with a test case.
9. Build System Fixes — Various .gyp, .cc files (~150 lines)
What: Collection of portability/correctness fixes:
- GCC LTO: removes
-fuse-linker-pluginfromcommon.gypi - ICU: adds
-fno-ltofor POSIX inicu-generic.gyp - ngtcp2: fixes
__popcntMSVC ARM conflict - V8
CSA_HOLE_SECURITY_CHECK: strips__FILE__/__LINE__for reproducible builds - V8
wasm-compiler.cc: C++20 designated initializer compat - V8
read-only-serializer.cc:memsetbeforememcpyfor deterministic output without pointer compression
Upstream path: Submit as individual PRs. These are general portability improvements, not pkg-specific. Some may already be fixed in newer dependency versions.
Upstream Strategy
Tier 1 — Submit Now (high acceptance, low risk)
-
child_process.fork()bug fix (patch 8) — one-line PR - Build fixes (patch 9) — individual small PRs for each fix
Tier 2 — Help Land VFS PR (highest impact)
- Help rebase nodejs/node#61478 (currently has merge conflicts)
- Fix worker thread VFS inheritance (comment)
- Contribute SEA-specific tests to VFS PR
- Help close remaining items in nodejs/node#62328
Landing VFS eliminates patches 2, 3, 7 and reduces the need for patch 1.
Tier 3 — RFC: V8 Sourceless Bytecode (high impact, needs socialization)
- Write RFC for bytecode-only execution mode
- Socialize in nodejs/node discussions
- Submit PR if RFC is accepted
Eliminates patch 4 (the most complex and fragile).
Tier 4 — Configure Flags (lower priority)
- Propose
--disable-inspectorconfigure flag - Or rely on SEA's existing inspector restrictions
Eliminates patches 5, 6.
Elimination Matrix
| Patch | Eliminated by | Depends on |
|---|---|---|
| 1. BAKERY | --build-sea |
SEA mode (already available) |
| 2. Bootstrap | node:vfs PR |
nodejs/node#61478 |
| 3. Module interception | node:vfs PR |
nodejs/node#61478 |
| 4. V8 sourceless | Upstream bytecode API | New RFC |
| 5. SIGUSR1 | --disable-inspector flag |
New PR or SEA restrictions |
| 6. Debug options | --disable-inspector flag |
New PR or SEA restrictions |
| 7. Init guards | node:vfs PR |
nodejs/node#61478 |
| 8. fork() bug | Bug fix PR | None — ready to submit |
| 9. Build fixes | Individual PRs | None — ready to submit |
End state: zero patches needed. pkg uses stock Node.js binaries via --build-sea + node:vfs.
Activity
build: remove redundant -fuse-linker-plugin from GCC LTO flags
robertsLando commentedon Apr 10, 2026
Progress Update
Tier 1 — Submit Now
child_process.fork()bug fix (patch 8) — child_process: use module.exports.spawn in fork and execFile nodejs/node#62666-fuse-linker-pluginremoval (patch 9) — build: remove redundant -fuse-linker-plugin from GCC LTO flags nodejs/node#62667ngtcp2— already fixed in current ngtcp2 version bundled in Node.js__popcntMSVC ARMV8— already fixed in current V8 versionCSA_HOLE_SECURITY_CHECK__FILE__/__LINE__V8— already fixed in current V8 versionwasm-compiler.ccC++20 designated initializerV8— already fixed in current V8 versionread-only-serializer.ccmemset/memcpy-fno-lto— needs a concrete build failure report to justify upstreamTier 2 — Help Land VFS PR
Tier 3 — RFC: V8 Sourceless Bytecode
sourceless: trueoption onvm.Script,vm.compileFunction(), andvm.SourceTextModule, gated behind--experimental-vm-sourceless. ~60 LOC V8 diff scoped behind per-isolate runtime checks. If landed, eliminates patch 4 entirely.Tier 4 — Configure Flags
--disable-inspectorconfigure flagbuild: remove redundant -fuse-linker-plugin from GCC LTO flags
docs: host documentation on GitHub Pages with VitePress
docs: trim docs-site verbosity and split architecture doc
docs: host documentation on GitHub Pages with VitePress (#234)
Niek commentedon Apr 15, 2026
Thanks for the great work - just following this issue closely.
build: remove redundant -fuse-linker-plugin from GCC LTO flags
build: remove redundant -fuse-linker-plugin from GCC LTO flags
build: remove redundant -fuse-linker-plugin from GCC LTO flags