[Windows] Three `detached: true` spawns missing `windowsHide` — telemetry flushSync opens a console window on nearly every CLI exit
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 78/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Quiet
- Tech stack
- node.js, typescript
- Domain
- cli
Research direction
Inspect the detached spawns in src/telemetry/transport.ts, src/utils/openBrowser.ts, and src/commands/previewLifecycle.ts, comparing their options with the existing windowsHide handling in src/utils/autoUpdate.ts. Verify on Windows that telemetry flushes, background preview, and browser launches no longer open console windows while preserving their existing detached behavior.
Written by the indexing model from the issue text.
Description
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.detachedtotruemakes 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: truein~/.hyperframes/config.json - Run any short command, e.g.
npx hyperframes check .ornpx 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 [email protected] — 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)
- Dominant language
- TypeScript
- Stars
- 54.1k
- Forks
- 4.9k
- Avg merge
- 7h 29m
- Merged PRs (30d)
- 778
Getting set up
This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from heygen-com/hyperframes
-
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
heygen-com/hyperframes#5027 ·
Maintainers usually reply within 1 day
-
fix(producer): propagate useGpu to HDR layered streaming encoderPossibly taken @Monster-GM claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 87/100
heygen-com/hyperframes#5002 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
heygen-com/hyperframes#4702 · 1 comment · 1 reaction ·
Maintainers usually reply within 1 day
-
Studio catalog prompt editor has no accessible namePossibly taken @lorenzozanee claimed this 11 days ago. Openbug difficulty/easy triage/ready
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
heygen-com/hyperframes#4384 ·
Maintainers usually reply within 1 day
-
lint: validate composition variables declared on supported root elementsMay be free again A pull request for this issue was closed without being merged. Openbug difficulty/easy triage/ready
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
heygen-com/hyperframes#4383 ·
Maintainers usually reply within 1 day
All issues in heygen-com/hyperframes
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day
-
bug:new
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
callstackincubator/simlock#350 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
openwatersio/maritime-zones#33 ·
Maintainers usually reply within 1 day
-
Booking email verification fails for plus aliases with impersonation protection enabledPossibly taken @kankadev claimed this today. Open
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
calcom/cal.diy#30293 · 1 comment ·
Maintainers usually reply within 5 days
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
AOSSIE-Org/DebateAI#611 ·
Maintainers usually reply within 3 days