Node --build-sea Not Working? Fix It on Node 24 LTS
Node --build-sea throws "bad option" on Node 24 LTS. Here's the version-detection fallback script and a real migration timeline for Node 26.

You run node --build-sea sea-config.json on your CI box, and Node throws back bad option: --build-sea. You didn't misspell anything. That flag simply doesn't exist yet on the LTS line most production teams are running, and the docs don't make that obvious until you've already burned twenty minutes on it.
Here's the situation: Node folded its Single Executable Application (SEA) build process into a single core flag, --build-sea, but that flag shipped starting with Node 25.5 and currently only lives on Node 26, the Current release. Node 24 "Krypton," which is Active LTS as of today, only understands the older --experimental-sea-config flag. If you're building SEAs for anything that touches a production LTS pipeline, you're choosing between two genuinely different workflows right now, and picking wrong costs you either broken builds or unnecessary complexity. Let's compare them properly.
What Each Workflow Actually Requires
The Node 26 path is almost insultingly simple. You write a sea-config.json pointing at your bundled entry file and an output binary path, run node --build-sea sea-config.json, and Node handles blob generation, binary copying, and injection internally using the LIEF library for binary patching. On macOS you still need a manual codesign --sign - dist/hello afterward, since code signing was never part of the SEA spec to begin with. That's the entire build on Current.
The Node 24 path is the one everyone building SEA tooling has been living with for over a year. It takes five distinct steps instead of one.
First, you run node --experimental-sea-config sea-config.json to generate a blob file. Second, you copy the actual node binary from wherever it lives on the target machine into your output path. Third, on macOS, you strip that binary's existing code signature with codesign --remove-signature, because you're about to mutate it.
Fourth, you inject the blob using postject, an external WASM-based tool pulled in via npx, passing a sentinel fuse string (NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2) copied from documentation and trusted blindly. Fifth, you re-sign the binary.
The config file itself differs too. On the old path, sea-config.json points its output at an intermediate .blob file that postject then injects into a binary you've separately assembled. On the new path, output points directly at your final executable, and Node handles everything internal to that single file. If you're maintaining a build script that assumes the old blob-and-inject shape, moving straight to Node 26 syntax without updating the config will silently produce the wrong thing.
Dependency Risk and Maintenance Burden
This is where the two paths diverge hardest, and it's not just about step count. The Node team has said openly that postject had become effectively unmaintained, with issues accumulating and no clear ownership going forward. If your build pipeline depends on npx postject firing correctly on every CI run, you're depending on a small external package that nobody is actively steering, plus a magic sentinel string you're trusting without verifying it against anything.
--build-sea on Node 26 removes that dependency entirely. Node vendors the same binary-patching logic that postject used to provide, so there's no npm install step, no network fetch during build, and no separate tool version to track against your Node version. For teams that care about supply-chain surface area in CI, that's not a minor convenience — it's one fewer external moving part that could break a release build at 4 p.m. on a Friday. This pairs well with byzantine quorum size formula: why 3-of-4 beats 2-of-3.
The tradeoff is that the LTS path is battle-tested precisely because it's been the only option for so long. If your build already works on Node 24 with postject pinned to a specific version, it's not fragile just because it's manual. It becomes fragile only if you're not pinning postject's version and a breaking update lands mid-pipeline — a real risk worth locking down regardless of which flag you eventually migrate to.
Writing a Build Script That Detects the Version
If you support both LTS and Current, or you're not sure which Node version your CI runner will have next quarter, hardcoding either path is a mistake. The practical fix is a build script that checks process.version, or better, actually probes for the flag's availability, and branches accordingly. This pairs well with our guide to graphrag vs vector rag: when to use each one.
const { execSync } = require('child_process');
function supportsBuildSea() {
const [major] = process.versions.node.split('.').map(Number);
return major >= 26;
}
if (supportsBuildSea()) {
execSync('node --build-sea sea-config.json', { stdio: 'inherit' });
} else {
execSync('node --experimental-sea-config sea-config.json', { stdio: 'inherit' });
const nodePath = execSync('command -v node').toString().trim();
require('fs').copyFileSync(nodePath, 'dist/hello');
if (process.platform === 'darwin') {
execSync('codesign --remove-signature dist/hello');
}
execSync(
'npx --yes postject dist/hello NODE_SEA_BLOB dist/sea/sea-prep.blob ' +
'--sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 ' +
'--macho-segment-name NODE_SEA'
);
}
if (process.platform === 'darwin') {
execSync('codesign --sign - dist/hello');
}
A version check on the major number is simpler and more reliable than trying to catch the "bad option" error and parse stderr, since that couples your script to Node's exact error message format. You'll also need two slightly different sea-config.json shapes if you're supporting both paths cleanly, since the output field's meaning shifts between blob and final binary. The cleanest approach is generating that config file dynamically inside the script rather than committing a static JSON file, so the branch logic and the config logic stay in sync.
When It's Actually Safe to Drop the Fallback
Node 26 is an even-numbered release, and Node's release cadence promotes even-numbered versions to Active LTS every October. That puts Node 26's LTS graduation in October 2026, a matter of weeks out from today. Once that happens, you can reasonably start requiring Node 26 as your build-time minimum for any tooling you control end to end, like internal CLIs or scripts you run only in your own CI.
Also read: also worth reading: ai code review checklist: what to automate vs block
That's different from safely dropping the fallback logic in a published boilerplate or open source tool other people install on their own machines. Plenty of production fleets stay on the previous LTS well past a new one's graduation date, since nobody rushes major runtime upgrades the week they become available. A safe rule of thumb: keep the fallback branch alive for at least one full LTS cycle after Node 26 graduates — realistically through mid-to-late 2027 — unless you have direct control over every machine running your build.
Node's stability index still marks SEA itself as Experimental, specifically stability level 1.1, "Active development," not Stable. That status applies to both build paths, so migrating to --build-sea doesn't buy you an API guarantee. It buys you a simpler build with fewer external dependencies, but the underlying feature can still shift shape before it's frozen.
If you're shipping a tool other people install today, keep the version-detection fallback in your build script through at least the next year, since you don't control your users' Node version. If you only build on infrastructure you control and can force a runtime upgrade, wait for Node 26's LTS graduation in October 2026, confirm your codesign and packaging steps still work, and then delete the postject branch entirely. Either way, don't hand-roll the blob-and-inject dance longer than you have to. It was already legacy the moment Node absorbed it into core.
Related Articles

How to Use Claude Code Subagents to Parallelize Development
Learn how to enhance your development workflow using Claude Code Subagents. This guide provides practical examples for parallelizing coding tasks.
Sep 13, 2025

Unlocking ChatGPT Developer Mode: Full MCP Client Access
Unlock the power of ChatGPT Developer Mode with full MCP client access. Discover how to enhance your coding projects and streamline development.
Sep 11, 2025

Stack Overflow's New Learning Resources for Coders
Explore the latest learning tools from Stack Overflow designed to empower coders with hands-on exercises, expanded topics, and practical insights.
Sep 10, 2025