fix(host-service): add shell-ready timeout so preset commands can't be dropped forever
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.zloginnever 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 startspending- On expiry the readiness promise resolves as a new
timed_outstate 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, allcancelShellReadyteardown paths); a late timer no-ops after cancellation
Verification
bun run lintclean,tsc --noEmitinpackages/host-servicepassesterminal.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_outand 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. queueInitialCommandnow proceeds onready,timed_out, orunsupported; only session teardown cancels it.
- Keep the first-prompt wait but bound it at 15s; on expiry resolve as
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.