fix(host-service): add shell-ready timeout so preset commands can't be dropped forever

Merged
SA
3mo ago
+55-6

Problem

Since desktop-v1.16.1 (published 2026-07-21), launching an agent preset (e.g. the Claude preset) can open a terminal that never runs the agent command — the user is left with a plain empty shell.

Root cause: #5774 reintroduced marker-gating of initialCommand in queueInitialCommand — the exact behavior #4963 removed ("gating turned broken/missing markers into a guaranteed stall") — and additionally deleted the timeout fallback. The command waits on shellReadyPromise, which only resolves when the OSC 133;A prompt marker reaches the scanner. shellLaunchExpectsReadyMarker only checks that wrapper files exist on disk; it cannot guarantee the marker is ever emitted:

  • user rc execs another process (tmux, another shell), so our .zlogin never runs
  • user config re-points ZDOTDIR
  • rc files blocking on stdin / broken hooks (all listed in #4963's commit message)

For affected shell configs the drop is deterministic: the PTY and shell start fine, but the queued command never writes and is never retried. Third regression of this bug (v1 #3478, v2 #4963, now #5774).

Fix

Keep #5774's first-prompt wait — it's a real fix for direnv/devenv startup hooks consuming PTY input — but bound it:

  • SHELL_READY_TIMEOUT_MS = 15_000 (same budget as the v1 stack's gate, covers Nix devenv via direnv) armed when a session starts pending
  • On expiry the readiness promise resolves as a new timed_out state and the queued command writes anyway — failure mode degrades from "dropped forever" to "delayed at most 15s"
  • Restores the flush of partially-matched marker bytes the scanner withholds (deleted by #5774), so a never-completed marker prefix isn't dropped from output
  • Timer cleared on every exit from pending (marker match, timeout, all cancelShellReady teardown paths); a late timer no-ops after cancellation

Verification

  • bun run lint clean, tsc --noEmit in packages/host-service passes
  • terminal.adoption.node-test.ts (holds the #4963/#5774 regression tests) could not run locally — better-sqlite3 in this worktree is built for the Electron ABI; relying on CI for that suite
  • No live end-to-end repro (needs a shell config that suppresses the marker)

https://claude.ai/code/session_01KEmzD2DhH34tRA6GUuRgri


Summary by cubic

Adds a 15s shell-ready timeout so preset/agent commands always run even if the OSC 133;A prompt marker never arrives. Prevents terminals that launch into an empty shell with no command executed (regression from #5774).

  • Bug Fixes
    • Keep the first-prompt wait but bound it at 15s; on expiry resolve as timed_out and run the queued command.
    • Flush any partial marker bytes the scanner was holding so output isn’t lost.
    • Clear the timeout on every exit from pending (ready, timed_out, cancelled); late timers no-op.
    • queueInitialCommand now proceeds on ready, timed_out, or unsupported; only session teardown cancels it.

Summary by CodeRabbit

  • Bug Fixes
    • Added a 15-second fallback when shell readiness cannot be detected.
    • Prevented terminal output from being lost when readiness detection times out.
    • Improved cleanup of readiness timers when terminal sessions are cancelled or closed.