Describe the bug
On Windows, console windows pop up during ordinary CLI use — not just during renders. This is a follow-up to #3379 (ffmpeg, fixed) and is distinct from #3430 (chrome-headless-shell, still open): those two cover console-subsystem binaries, whereas this one is about detached: true spawns of Node itself.
Node's docs are explicit about the mechanism:
On Windows, setting options.detached to true makes it possible for the child process to continue running after the parent exits. The child will have its own console window.
child_process.spawn() defaults windowsHide to false, so any detached: true spawn opens a visible console window on Windows unless windowsHide: true is passed.
Root cause
packages/cli v0.8.13 has four detached: true spawns. Three are missing windowsHide: true:
| Source |
What it spawns |
windowsHide |
src/telemetry/transport.ts (flushSync) |
node -e "fetch(...)" → PostHog |
❌ missing |
src/utils/openBrowser.ts (openBrowser) |
user's browser |
❌ missing |
src/commands/previewLifecycle.ts |
background preview server |
❌ missing |
src/utils/autoUpdate.ts |
update check |
✅ present |
autoUpdate.ts already gets this right, which makes the other three look like oversights rather than intent.
The worst offender: telemetry flushSync
flushSync() is the process-exit flush path. Because it can't await an HTTP POST during shutdown, it hands the payload to a detached Node process to deliver after the parent dies:
function flushSync() {
const payload = buildPayload(eventQueue);
if (payload == null) return;
eventQueue = [];
try {
const child = spawn(
process.execPath,
["-e", `fetch(${JSON.stringify(`${POSTHOG_HOST}/batch/`)},{...})`],
{ detached: true, stdio: "ignore" } // <-- no windowsHide
);
child.unref();
} catch {}
}
The strategy itself is sound — the only thing missing is windowsHide: true.
Impact is amplified by how the CLI is actually used. Agent-driven workflows invoke many short-lived commands (check, lint, keyframes, capture, snapshot, beats, preview), and each invocation that exits with queued events opens its own console window. My ~/.hyperframes/config.json shows commandCount: 1350, which is a lot of windows. Unlike #3430 this isn't render-only — it fires on nearly every command, including read-only ones.
Steps to reproduce
- On Windows, ensure telemetry is on (default):
telemetryEnabled: true in ~/.hyperframes/config.json
- Run any short command, e.g.
npx hyperframes check . or npx hyperframes lint .
- Watch the desktop/taskbar as the command exits — a console window flashes open
- Run several commands in sequence; you get one window per exit
Setting HYPERFRAMES_NO_TELEMETRY=1 (or DO_NOT_TRACK=1) makes the flashing stop, which isolates the telemetry spawn as the cause.
For the other two sites: hyperframes preview without --foreground spawns the detached background server (one window), and without --no-open spawns the browser via options.browserPath (one more).
Expected behavior
No console window appears for any of these background spawns.
Actual behavior
A console window opens per detached spawn — in the telemetry case, on nearly every CLI invocation.
Suggested fix
Add windowsHide: true at the three call sites, matching what autoUpdate.ts already does:
// src/telemetry/transport.ts
const child = spawn(process.execPath, ["-e", script], {
detached: true,
stdio: "ignore",
windowsHide: true,
});
// src/utils/openBrowser.ts
const child = spawn7(options.browserPath, args, {
detached: true,
stdio: "ignore",
windowsHide: true,
});
// src/commands/previewLifecycle.ts
child = spawn17(execPath, args, {
detached: true,
stdio: ["ignore", logFd, logFd],
env: process.env,
windowsHide: true,
});
windowsHide is a no-op on macOS/Linux, so it's safe to apply unconditionally — same rationale as the #3379 fix.
Given that #3379, #3430, and this issue are all the same class of defect, it may be worth a small shared spawn helper that applies windowsHide: true by default, plus a lint rule that flags a bare detached: true without it.
Environment
hyperframes 0.8.13 (latest)
Node.js v24.15.0 (win32 x64)
OS Microsoft Windows 10 Home 10.0.19045 (build 19045)
Install npx (_npx cache)
Additional context
Verified by static analysis of the shipped dist/cli.js in hyperframes@0.8.13 — all four detached: true sites were inspected directly, along with every windowsHide occurrence. All ffmpeg/ffprobe spawns correctly carry windowsHide: true with the explanatory comment added by the #3379 fix, so that regression has not returned.
Related: #3379 (ffmpeg, closed/fixed) · #3430 (chrome-headless-shell, open)
Describe the bug
On Windows, console windows pop up during ordinary CLI use — not just during renders. This is a follow-up to #3379 (ffmpeg, fixed) and is distinct from #3430 (chrome-headless-shell, still open): those two cover console-subsystem binaries, whereas this one is about
detached: truespawns of Node itself.Node's docs are explicit about the mechanism:
child_process.spawn()defaultswindowsHidetofalse, so anydetached: truespawn opens a visible console window on Windows unlesswindowsHide: trueis passed.Root cause
packages/cliv0.8.13 has fourdetached: truespawns. Three are missingwindowsHide: true:windowsHidesrc/telemetry/transport.ts(flushSync)node -e "fetch(...)"→ PostHogsrc/utils/openBrowser.ts(openBrowser)src/commands/previewLifecycle.tssrc/utils/autoUpdate.tsautoUpdate.tsalready gets this right, which makes the other three look like oversights rather than intent.The worst offender: telemetry
flushSyncflushSync()is the process-exit flush path. Because it can'tawaitan HTTP POST during shutdown, it hands the payload to a detached Node process to deliver after the parent dies:The strategy itself is sound — the only thing missing is
windowsHide: true.Impact is amplified by how the CLI is actually used. Agent-driven workflows invoke many short-lived commands (
check,lint,keyframes,capture,snapshot,beats,preview), and each invocation that exits with queued events opens its own console window. My~/.hyperframes/config.jsonshowscommandCount: 1350, which is a lot of windows. Unlike #3430 this isn't render-only — it fires on nearly every command, including read-only ones.Steps to reproduce
telemetryEnabled: truein~/.hyperframes/config.jsonnpx hyperframes check .ornpx hyperframes lint .Setting
HYPERFRAMES_NO_TELEMETRY=1(orDO_NOT_TRACK=1) makes the flashing stop, which isolates the telemetry spawn as the cause.For the other two sites:
hyperframes previewwithout--foregroundspawns the detached background server (one window), and without--no-openspawns the browser viaoptions.browserPath(one more).Expected behavior
No console window appears for any of these background spawns.
Actual behavior
A console window opens per detached spawn — in the telemetry case, on nearly every CLI invocation.
Suggested fix
Add
windowsHide: trueat the three call sites, matching whatautoUpdate.tsalready does:windowsHideis a no-op on macOS/Linux, so it's safe to apply unconditionally — same rationale as the #3379 fix.Given that #3379, #3430, and this issue are all the same class of defect, it may be worth a small shared spawn helper that applies
windowsHide: trueby default, plus a lint rule that flags a baredetached: truewithout it.Environment
Additional context
Verified by static analysis of the shipped
dist/cli.jsinhyperframes@0.8.13— all fourdetached: truesites were inspected directly, along with everywindowsHideoccurrence. All ffmpeg/ffprobe spawns correctly carrywindowsHide: truewith the explanatory comment added by the #3379 fix, so that regression has not returned.Related: #3379 (ffmpeg, closed/fixed) · #3430 (chrome-headless-shell, open)