fix(dev): pass --inspect as CLI flag, not via NODE_OPTIONS

NODE_OPTIONS is inherited by every grandchild node process. With --inspect
in there, every vitest worker, agent subprocess, and claude invocation the
dashboard spawned tried to bind 127.0.0.1:9229 and emitted "address already
in use" — those failure strings ended up in agent logs (visible in heap
diagnostics). Pass inspector flags directly on the node command line so
they apply only to the dashboard process.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-04-25 08:32:07 -07:00
parent d1a730e5c9
commit 3324bc4dec

View File

@@ -18,8 +18,10 @@ const rawArgs = process.argv.slice(2);
// --inspect / --inspect-brk / --inspect=PORT enables the Node inspector and // --inspect / --inspect-brk / --inspect=PORT enables the Node inspector and
// auto-dumps a heap snapshot just before the heap limit is hit. Strip these // auto-dumps a heap snapshot just before the heap limit is hit. Strip these
// from forwarded args so they don't reach the dashboard CLI parser; they go // from forwarded args so they don't reach the dashboard CLI parser. We pass
// into NODE_OPTIONS instead so tsx's child node process picks them up. // them as CLI flags directly to node (NOT via NODE_OPTIONS), because
// NODE_OPTIONS is inherited by every grandchild process — every vitest /
// agent / claude subprocess would then try to bind 9229 and fail.
const inspectFlags = []; const inspectFlags = [];
const args = []; const args = [];
for (const a of rawArgs) { for (const a of rawArgs) {
@@ -33,15 +35,13 @@ if (inspectFlags.length > 0) {
// 3 = take up to 3 snapshots as we approach the heap limit. Files land in // 3 = take up to 3 snapshots as we approach the heap limit. Files land in
// CWD as Heap.YYYYMMDD.HHMMSS.PID.NNN.heapsnapshot // CWD as Heap.YYYYMMDD.HHMMSS.PID.NNN.heapsnapshot
inspectFlags.push("--heapsnapshot-near-heap-limit=3"); inspectFlags.push("--heapsnapshot-near-heap-limit=3");
console.log(`[dev-with-memory] inspector enabled: ${inspectFlags.join(" ")}`); console.log(`[dev-with-memory] inspector flags: ${inspectFlags.join(" ")}`);
} }
// Base NODE_OPTIONS applied to every spawned node process (build + run). // NODE_OPTIONS is shared with every spawned node process (build + run +
// Inspector flags are NOT here — they go only on the final tsx run, otherwise // agents). Heap size belongs here. Inspector flags do NOT — see comment above.
// `pnpm build` would grab port 9229 first and tsx would fail to bind. const nodeOptions = `--max-old-space-size=${MEMORY_MB} ${process.env.NODE_OPTIONS || ""}`.trim();
const baseNodeOptions = `--max-old-space-size=${MEMORY_MB} ${process.env.NODE_OPTIONS || ""}`.trim(); process.env.NODE_OPTIONS = nodeOptions;
process.env.NODE_OPTIONS = baseNodeOptions;
const runNodeOptions = `${baseNodeOptions} ${inspectFlags.join(" ")}`.trim();
// In dev we bind the dashboard to 0.0.0.0 so the server is reachable from // In dev we bind the dashboard to 0.0.0.0 so the server is reachable from
// mobile devices and other machines on the LAN for testing. Production // mobile devices and other machines on the LAN for testing. Production
@@ -66,16 +66,16 @@ const ENTRY = path.resolve(process.cwd(), "packages/cli/src/bin.ts");
// Spawn node directly (no shell) so the inspector attaches to the real app // Spawn node directly (no shell) so the inspector attaches to the real app
// process and there's no parent/child wrapper consuming --inspect. // process and there's no parent/child wrapper consuming --inspect.
// Inspector flags are CLI args here so they apply only to this process and
// don't propagate to grandchildren via NODE_OPTIONS.
function runApp(extraArgs) { function runApp(extraArgs) {
const tsx = spawn(process.execPath, [ const tsx = spawn(process.execPath, [
...inspectFlags,
"--require", PRELOAD, "--require", PRELOAD,
"--import", `file://${LOADER}`, "--import", `file://${LOADER}`,
ENTRY, ENTRY,
...extraArgs, ...extraArgs,
], { ], { stdio: "inherit" });
stdio: "inherit",
env: { ...process.env, NODE_OPTIONS: runNodeOptions },
});
tsx.on("close", (c) => process.exit(c ?? 1)); tsx.on("close", (c) => process.exit(c ?? 1));
} }