Skip to content

before-quit doesn't await pty kills: claude/codex orphans on Cmd+Q #64

@VincentShipsIt

Description

@VincentShipsIt

Bug

apps/desktop/src/main/index.ts before-quit handler called processManager?.killAll() synchronously and then returned. killAll() iterates the registry and invokes pty.kill() on each — but pty.kill() only sends SIGHUP; the actual child exit is async.

Result on Cmd+Q with active pipelines:

  1. Electron main process exits within milliseconds.
  2. claude / codex / gh pty children never get a chance to exit.
  3. They become orphaned subprocesses owned by init, still consuming CPU + holding worktree file locks.

There was also no SIGKILL escalation — a wedged pty would survive forever.

Fix (already merged on develop)

packages/agents/src/process-manager.ts — added killAllAndWait(graceMs = 5000):

  1. Send pty.kill() (SIGHUP) to every active process.
  2. Wait for each to emit exit.
  3. After graceMs, escalate any holdouts to pty.kill('SIGKILL').
  4. Final 1s cap, then resolve.

apps/desktop/src/main/index.ts — rewrote before-quit:

  • Always event.preventDefault() on the first pass so we can wait.
  • await processManager?.killAllAndWait(5000) before exiting.
  • app.exit(0) (synchronous, bypasses before-quit) once children are gone.
  • killInFlight guard prevents the dialog logic from re-running on the second pass.

New tests in packages/agents/src/process-manager.test.ts cover: resolve-after-all-exit, no-op when nothing active, and SIGKILL escalation under fake timers.

Worktree cleanup audit

Considered cleaning worktrees on quit. Decided against it — worktrees are keyed by threadId and intentionally persist across sessions per .agents/memory/worktrees.md. Auto-cleanup would orphan threads.

Verification

bun run test src/process-manager.test.ts   # 7/7 pass
bun run typecheck                          # repo-wide clean

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status

    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions