fix(host-service): send initialCommand Enter as a separate delayed write

Merged
SA
3mo ago
+32-8

What

Agent and preset launches could end up typed at the prompt but never executed: the workspace was created, the agent terminal opened, and the full launch command ('claude' '--dangerously-skip-permissions' '<prompt>') sat in the shell's edit buffer with the cursor blinking after it. Reported as "the new workspace page creates a workspace but doesn't start the agent" — but it's page-agnostic and hits every initialCommand consumer (modal creates, automations, setup terminals).

Root cause

The OSC 133;A shell-ready marker is emitted from a precmd hook, which runs before the prompt is drawn and before the line editor starts reading input. Writing command\n in that gap lets shell plugin init (oh-my-zsh vi-mode, syntax-highlighting — worst under first-create load) flush the PTY input queue mid-read: the command text survives into the editor's buffer, the trailing newline is lost.

Confirmed live via CDP against the dev app: on the first workspace create after boot the command was stuck unexecuted; sending one synthetic Enter keypress launched Claude immediately, which then completed the prompt's task — proving only the newline was missing. This is the residual hole in the #4963 → #5774 (first-prompt gate) → #5927 (15s bound) series.

Fix

In queueInitialCommand:

  • strip trailing newlines from the command text and write it alone;
  • 500ms later, send Enter as its own write — and as \r (what a real Enter key sends, bound to accept-line in every keymap) instead of \n (relies on the ^J binding).

Exactly one Enter is ever sent, so double-execution is impossible. If startup eats the text itself, the stray \r is a no-op at an empty prompt — every failure mode is strictly no-worse than before. Same technique VS Code shell integration uses; v1 avoided the class entirely by launching via shell -lc, which is why v1 never hit this.

Verification

  • bun run test:e2e (host-service): 20/20 pass, including the direnv input-consumption simulation and the promptness bound (command executed in 517ms with the new delay).
  • End-to-end via CDP on the dev desktop app: pre-fix, first create after boot reproduced the stuck command (screenshot evidence, before/after Enter). Post-fix, restarted the app and ran the full /new-workspace flow twice with real input events — both creates launched Claude, which replied within ~2s. Setup terminals unaffected.
  • Biome + tsc clean.

https://claude.ai/code/session_01CwWxQTQW2zLeN9hhRkQNxT


Open in Stage

Summary by cubic

Fixes an intermittent issue where the initial launch command was typed but not executed. We now send the command text and Enter separately with a short delay for reliable execution.

  • Bug Fixes
    • In packages/host-service/src/terminal/terminal.ts, strip trailing CR/LF from initialCommand and write only the text.
    • Send Enter as a separate \r 500 ms after shell-ready to avoid plugin init eating it.
    • Update packages/host-service/test/integration/setup-scripts.integration.test.ts to assert split write and delayed \r; tidy assertion formatting.
    • Applies to all initialCommand flows (new workspace, modal creates, automations, setup terminals).

Summary by CodeRabbit

  • Bug Fixes
    • Improved reliability of terminal “initial command” execution by sending the command text and the Enter keystroke as two separate, ordered writes.
    • Normalized the command text to remove any trailing CR/LF characters before sending, and added a brief delay for better alignment with shell readiness.
  • Tests
    • Updated the integration test to assert the command is written without \n, and that the Enter (\r) write occurs afterward.