Skip to content

fix(cli): force exit after render to prevent process hang - #104

Merged
miguel-heygen merged 2 commits into
mainfrom
fix/cli-render-exit
Mar 27, 2026
Merged

fix(cli): force exit after render to prevent process hang#104
miguel-heygen merged 2 commits into
mainfrom
fix/cli-render-exit

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Mar 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Adds process.exit(0) after render completes in the CLI render command
  • Fixes the process hanging indefinitely after npx hyperframes render --output video.mp4
  • Root cause: Node.js fetch() keep-alive pool (from update checks and telemetry) keeps TCP connections open, preventing the event loop from draining
  • Telemetry is preserved — the exit handler in cli.ts calls flushSync() which spawns a detached child process

Test plan

  • Run npx hyperframes render --output test.mp4 and verify the process exits after completion
  • Verify telemetry events are still sent (check PostHog)

Node.js keeps the process alive due to lingering HTTP connections from
fetch() keep-alive pool (update checks and telemetry). The exit event
handler in cli.ts fires flushSync() which spawns a detached child
process so telemetry data is not lost.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@jrusso1020

Copy link
Copy Markdown
Collaborator

The diagnosis is correct — Node.js fetch() (undici) keeps TCP sockets alive in a connection pool, which holds the event loop open and prevents the process from exiting.

However, process.exit(0) is a band-aid. The root cause is that our two fetch() call sites don't close their connections:

  1. updateCheck.tsfetch(NPM_REGISTRY_URL, ...) fires at startup
  2. telemetry/client.tsfetch(POSTHOG_HOST/batch/, ...) fires on flush

Adding Connection: "close" to both fetch calls tells undici not to pool the socket, so it closes after the response completes. With that fix, the event loop drains naturally after render:

// updateCheck.ts
const res = await fetch(NPM_REGISTRY_URL, {
  signal: controller.signal,
  headers: { Connection: "close" },
});

// telemetry/client.ts
await fetch(`${POSTHOG_HOST}/batch/`, {
  method: "POST",
  headers: { "Content-Type": "application/json", Connection: "close" },
  body: JSON.stringify({ api_key: POSTHOG_API_KEY, batch }),
  signal: controller.signal,
});

With this, the flow becomes:

  1. Update check fetch runs → Connection: close → socket closes after response
  2. Render completes → event loop drains → beforeExit fires
  3. Telemetry flush() runs → also Connection: close → socket closes
  4. Event loop drains → process exits cleanly

No process.exit(0) needed, and the flushSync() detached-process fallback in the exit handler stays as a safety net for process.exit(1) calls in error paths.

…t(0)

Address PR feedback: the root cause is undici's HTTP keep-alive pool
holding TCP sockets open. Adding Connection: close to the update check
and telemetry fetch calls lets the event loop drain naturally after
render completes, without needing a forced process.exit(0).

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@miguel-heygen
miguel-heygen merged commit 2f22c3b into main Mar 27, 2026
14 checks passed
dahans-msft2 pushed a commit to dahans-msft2/hyperframes that referenced this pull request Aug 6, 2026
…#104)

## Summary

- Adds `process.exit(0)` after render completes in the CLI `render` command
- Fixes the process hanging indefinitely after `npx hyperframes render --output video.mp4`
- Root cause: Node.js `fetch()` keep-alive pool (from update checks and telemetry) keeps TCP connections open, preventing the event loop from draining
- Telemetry is preserved — the `exit` handler in `cli.ts` calls `flushSync()` which spawns a detached child process

## Test plan

- [x] Run `npx hyperframes render --output test.mp4` and verify the process exits after completion
- [x] Verify telemetry events are still sent (check PostHog)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants