fix(host-service): wait for shell readiness before preset commands

Merged
RO
4mo ago
+262-53

What & why

Preset commands were written to the PTY immediately after spawning the login shell. That assumed stdin would remain buffered until shell initialization completed, but interactive startup tools can read or flush queued input before the first prompt. In worktrees using direnv/devenv, the preset command was visibly echoed and then disappeared without launching.

This change makes readiness capability-based rather than shell-name- or timeout-based:

  • verify that the exact zsh/bash launch wrappers contain Superset's OSC 133;A prompt marker; fish remains verified through its injected init command
  • wait for the first prompt marker before writing an initial preset command when that marker is guaranteed
  • retain immediate launch behavior for missing, stale, or unsupported wrappers
  • avoid a fixed readiness deadline, so this works for any slow or interactive shell startup rather than only devenv

The result protects Claude and every other preset command from being consumed during shell initialization while preserving the no-marker fallback introduced for stale wrapper installs.

How I tested it

  • bun run lint
  • bun run typecheck (35/35 tasks successful)
  • bun test packages/host-service/src/terminal/env.test.ts (46 passing)
  • real PTY/adoption suite via the installed Superset Node runtime (19 passing), including:
    • delayed startup that consumes prematurely queued stdin
    • bash without a readiness marker launching promptly

Checklist

  • PR title follows conventional commits (type(scope): subject)
  • bun run lint and bun run typecheck pass (CI fails on lint warnings too)
  • "Allow edits from maintainers" is checked on fork PRs

Open in Stage

Summary by cubic

Waits for a verified shell prompt marker before sending preset commands, so interactive startup (e.g., direnv/devenv) can’t consume them. Falls back to immediate send when no marker is guaranteed or for adopted sessions; cancels queued commands if the shell exits or the daemon disconnects.

  • Bug Fixes
    • Added shellLaunchExpectsReadyMarker to verify zsh/bash wrappers actually emit OSC 133;A; fish is trusted via its injected init command.
    • Deferred initialCommand until the first verified marker; removed the timeout fallback and partial-marker flushing.
    • Introduced a cancellable readiness state; never writes after cancellation or exit.
    • Switched from shell-name heuristics to a capability check; kept immediate launch for missing/stale wrappers and adopted sessions.
    • Expanded tests for wrapper verification, delayed readiness, no-marker fallback, and exit-before-readiness; quoted sentinel paths in adoption tests to avoid tmpdir-with-spaces flakiness.

Summary by CodeRabbit

  • Bug Fixes
    • Improved terminal shell readiness detection by checking shell-specific wrapper content to confirm the expected OSC “ready” marker will be emitted (with correct handling for zsh, bash, and fish).
    • Updated the adoption flow so initial commands are queued until readiness is confirmed; readiness now cancels cleanly if the shell exits early.
    • Simplified readiness state transitions to avoid incorrect “ready” signals during partial/incomplete marker scenarios.
  • Tests
    • Added end-to-end tests for delayed marker emission and for cancellation when a shell exits before the marker appears.