Refuse to launch an agent whose CLI is missing, instead of a dead pane - #217
Merged
Merged
Conversation
A launch runs `<agent>; exec $SHELL -l` in a PTY, so a missing binary
became one line of shell output in a pane that then looked healthy: the
card was filed `running`, a loop tick reported "run N started", and
nothing said the agent never ran. An agent installed yesterday and gone
today (an uninstall, a PATH that moved under a reboot) failed silently
every hour.
Every launch now asks the login shell whether the binary resolves before
anything is written down. The probe uses the same shell and flags the
launch does, because probing /bin/sh with the daemon's own PATH answers a
different question and produces false absents on exactly the Macs whose
boot-time PATH probe missed. It reports present/absent/unknown and only a
definite absent refuses: a shell that fails to answer knows nothing about
the binary, and blocking work on that would be worse than the bug.
Three callers did irreversible bookkeeping before spawning, which a
refusal would have stranded:
- a loop persisted its task link only after the launch, so a failing
tick left it unlinked and the next one built another worktree;
- restore stamped the tab `restored` first, and that is a one-way door
off the restorable strip;
- the loop form's <select> omitted an uninstalled agent, so it showed a
different one while saving the missing one straight back.
Also gates the phone's agent picker, which offered agents the box hasn't
got while the desktop's has always disabled them.
The probe is a seam on Services so tests don't assert which CLIs happen
to be installed on the machine running them (a CI runner has none).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Reported as "command not found: opencode" in a task pane, on the phone against a box and locally after a reboot.
What was wrong
A launch runs
<agent>; exec $SHELL -lin a PTY, so a missing binary produced one line of shell output in a pane that then sat there looking healthy. The card was filedrunning, a loop tick reportedrun N startedwith statusok, and nothing anywhere said the agent never ran.spawnAgentInTaskvalidated the agent id against the registry, never the binary.That is not an exotic state: an agent installed yesterday can be gone today, via an uninstall or a PATH that moved under a reboot. No availability list captured at connect time can catch it, and the loop runner never consults one anyway.
The fix
Every launch asks the login shell whether the binary resolves, before anything is written down.
spawnAgentInTaskis the single choke point for the composer, relaunch, a restored tab, the phone and a loop tick, so one guard covers all of them.probeAgentBinary()uses the same shell and flags the launch does ($SHELL -lc 'command -v <bin>', the idiom the box installer already uses). Probing/bin/shwith the daemon's own PATH answers a different question and produces false absents on exactly the Macs whose boot-time PATH probe missed, and a false absent refuses a launch that would have worked.present | absent | unknown, and only a definite absent refuses. A shell that times out knows nothing about the binary; blocking work on that would be a worse bug than the one this prevents.Three side effects a refusal would have stranded
Each of these was already latent; the guard is what made them reachable.
restoredbefore spawning, and that is a one-way door off the restorable strip. Now stamped only once the replacement is up.<select>omitted an uninstalled agent, so it displayed a different one while the state still held the missing one, and Save wrote the missing one straight back.Also gates the phone's agent picker, which listed agents the box hasn't got with no indication; the desktop's has disabled them since #108.
Verification
claudedoes not exist). Two existing suites drove the real launch path withagentId: "claude"and would have gone red on CI, so the probe is a seam onServices, defaulting to the real one.opencode: the real path returnsOpenCode isn't installed here — `opencode` is not on PATH. Install it with: curl -fsSL https://opencode.ai/install | bash, writes no session row, and leaves the card in its column.claudestill launches.listAgents()148-153ms for three parallel probes, against ~0ms before. That lands on the handshake and the launch, both of which already do far slower work.