fix(host-service): keep v2 preset commands alive during shell init

SA
5mo ago
+44-0

Problem

In v2 workspaces, launching a terminal preset (e.g. claude) loses the command when the shell is still initializing (direnv / nvm / oh-my-zsh / powerlevel10k). This is the same class of bug #2469 fixed for the v1 terminal stack (apps/desktop/src/main/terminal-host) — but v2 workspaces run an entirely separate terminal stack in packages/host-service, which never received the fix.

Reported in Slack ("command doesn't live… thought we fixed this… v2 needs this I guess").

Root cause

During startup the shell emits terminal capability queries (DA \x1b[c, DSR/CPR \x1b[6n). The renderer's xterm answers them, and those replies arrive back as PTY stdin. The host-service stack forwarded all stdin unconditionally, so responses like ?62;4;9;22c landed on the command line ahead of the queued preset command and displaced it — the command never ran.

v1 already solved this. v2 was missing both halves of the solution:

  • v1 drops \x1b-prefixed stdin while the shell is pending (terminal-host/session.ts).
  • v1's headless emulator answers the shell's startup queries server-side, so dropping the renderer's duplicate is safe.

v2 had the shellReadyState machine and the OSC 133;A scanner wired for output, but never gated input, and its per-session headless xterm (modeTracker) only tracked modes — it never answered queries.

Fix

Mirror v1's two-part mechanism in the host-service stack:

  1. Drop query responses during init — \x1b-prefixed stdin is dropped while shellReadyState === "pending", in both input paths (writeInputToSession and the WebSocket "input" handler).
  2. Answer queries server-side during init — wire onData on the existing per-session headless xterm (modeTracker) and, while pending, write its DA/DSR replies back to the PTY. Gated on pending so the renderer stays the sole responder once the shell is interactive. This is the v2 analogue of v1's headless emulator.

The preset command itself is still written immediately (queueInitialCommand unchanged) — readiness only gates query responses, not the command. A broken/missing marker can't stall the launch, which is the failure mode earlier marker-gating attempts kept hitting (#3478 / #4963).

Files changed

FileChange
packages/host-service/src/terminal/terminal-mode-tracker.tsAdd onData to ModeTracker — surface the headless xterm's auto-replies to terminal queries
packages/host-service/src/terminal/terminal.tsAnswer startup queries server-side during pending; drop \x1b-prefixed stdin during pending in both input paths

Verification

  • Confirmed the per-session headless xterm answers DA → \x1b[?1;2c and CPR → \x1b[1;1R via onData when fed via writeSync (the path modeTracker.feed uses).
  • bun test src/terminal — 69 pass / 0 fail.
  • typecheck and lint clean.

Test plan

  • Open a v2 workspace whose repo has a slow shell init (.envrc + direnv, or oh-my-zsh/powerlevel10k) and launch a preset — command renders immediately and runs once the prompt appears, with no ?62;...c garbage on the line.
  • Repeat with a fast shell (no direnv) — command runs promptly.
  • Re-attach to an existing (already-ready) terminal — input flows normally, escape sequences not dropped.

Open in Stage

Summary by cubic

Keep v2 terminal preset commands alive during slow shell init by filtering escape-sequence responses and answering startup queries server-side in packages/host-service. Preset commands now run reliably once the prompt is ready, without ?62;...c noise.

  • Bug Fixes
    • Drop \x1b-prefixed stdin while shellReadyState === "pending" in both input paths (writeInputToSession, WebSocket "input").
    • Add onData to ModeTracker and, while pending, write its DA/DSR replies to the PTY so the host answers queries during init.
    • Leave command queuing unchanged; readiness gates only query responses.

Summary by CodeRabbit

  • New Features

    • Added terminal callback mechanism to register handlers for automatic reply data from terminal queries.
  • Bug Fixes

    • Improved terminal session initialization by handling startup queries on the server side, preventing escape sequences from being incorrectly forwarded as user input during shell startup.