Skip to content

fix(cli): force-exit safety net for all commands, not just init - #1396

Open
jared-outpost[bot] wants to merge 1 commit into
mainfrom
issue-1237-process-hang
Open

fix(cli): force-exit safety net for all commands, not just init#1396
jared-outpost[bot] wants to merge 1 commit into
mainfrom
issue-1237-process-hang

Conversation

@jared-outpost

@jared-outpost jared-outpost Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Ordinary commands (org list, project list, issue view, auth status, etc.) finish their work and write complete output, but the process never exits — a lingering handle (keep-alive sockets / a libuv refcount quirk on macOS + Bun) keeps the event loop referenced. This is the same class of hang as #782/#833, but for everyday commands rather than the init wizard.

Fix

The force-exit safety net already existed but was armed only for init via a request flag. This generalizes it:

  • Renamed src/lib/init/force-exit.tssrc/lib/force-exit.ts, collapsing the request/schedule pair into a single scheduleForceExit().
  • runCli's finally now calls scheduleForceExit() unconditionally, after all recovery middleware (auto-auth, scope recovery, retry) has reached a terminal result.
  • Removed the init-specific requestInitForceExit() call.

The timer is scheduled only after the awaited command resolves, and .unref() means it fires only when another handle keeps the loop alive past a drained command. So it stays a no-op on clean exits and never arms commands that intentionally keep running (their awaited work never resolves, so the finally is never reached). Guarded to macOS and NODE_ENV !== "test".

Tests

  • Rewrote test/lib/force-exit.test.ts for the simplified API (schedules unref'd 100ms timer on macOS outside tests; no-op elsewhere / in tests).
  • Updated test/commands/init.test.ts to drop the removed request-flag spy.
  • vitest run test/lib/force-exit.test.ts test/commands/init.test.ts → 48 passed.
  • biome check on the changed files → clean.

Closes #1237

Ordinary commands (org list, project list, auth status, etc.) finished
their work and wrote complete output but the process never exited —
lingering keep-alive sockets / a libuv refcount quirk on macOS+Bun kept
the event loop referenced. The existing force-exit safety net was armed
only for the init wizard.

Generalize the helper (lib/init/force-exit.ts -> lib/force-exit.ts) and
schedule it unconditionally in runCli's finally, after all recovery
middleware has reached a terminal result. The unref'd timer only fires
when a handle keeps the loop alive past a drained command, so it stays a
no-op on clean exits and never arms commands whose awaited work never
resolves.

Fixes #1237
@vercel

vercel Bot commented Aug 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cli Ready Ready Preview Aug 8, 2026 8:12am

Request Review

@jared-outpost
jared-outpost Bot marked this pull request as ready for review August 8, 2026 08:20
@jared-outpost

jared-outpost Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

CI green (Build success). Self-review clean. Promoted to ready-for-review. Added issue author @azataiot as reviewer.

@github-actions github-actions Bot added the risk: medium PR risk score: medium label Aug 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk: medium PR risk score: medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Commands hang after completing — output written, process never exits

0 participants