Stop Tervin reporting its own failures as the user's configuration - #33
Merged
Conversation
The gate panel filters on `!is_tervin` precisely so Tervin's own gate is never shown as something the user configured. Every failure line still read "Tervin hook", because `is_tervin` was false for exactly the runs where it mattered. Detection matched `--tervin-hook` anywhere in the hook response. The runtime only echoes the hook's command line back when the hook *blocked*; a hook that merely failed carries nothing but its own stderr. So the flag was there to find whenever the gate worked and gone every time it broke. Tervin took its own dead socket, found no flag, concluded the run was the user's, and reported its own failure as their configuration. The one moment the distinction carries weight is the one moment it was wrong. The hook client's stderr prefix is now a shared constant, matched at both ends. The two sides drifting apart is what caused this, and nothing else would catch it: each compiles perfectly well on its own. Denials keep their bare text, since that stderr is fed back to the agent verbatim and a marker would reach it too. The existing test covered only the blocking path, which is the path that already worked. Three tests now: a gate that failed, a gate whose message is built from the constant rather than a copy of it, and a user's own broken hook, which must still be reported as theirs. That last one is the half a wider match could quietly swallow. rust 682 to 685. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A user with five profiles in `agents.toml` was shown "No agent profile configured". The file was well formed and `ProfileConfig::load()` read it correctly. The profiles were fetched and then thrown away. `agents_overview` answered two unrelated questions in one command: what the user configured, and what is installed on this machine. The first is a file read. The second spawns a subprocess per agent and ends with `$SHELL -ic alias`, which sources the user's rc files. Any of that failing failed the whole command, the UI got an error instead of an overview, and the profiles went down with the probe. Worse when nothing failed outright: `Command::output()` has no timeout, so an rc file that blocks blocked the agents view behind it forever. The two are now separate commands. `agents_overview` reads the config and returns; `agents_discovery` does the probing. The store calls them in order and sets the profiles before the second is even requested, so a discovery failure lands on a screen that already shows the profiles. It is reported rather than swallowed, and it costs nothing above it. This is the structure that makes the bug unreachable, rather than a rescue path that has to remember to run. `$SHELL -ic alias` is also bounded now, at five seconds. Aliases are a convenience, and a shell that will not answer promptly is one that offers nothing. The helper drains the pipe on its own thread: a child that fills the pipe buffer waits for a reader, so polling for exit without draining would be waiting on a child that is waiting on us. "Nothing installed" and "not finished looking" are different answers, so the runtimes list says which one it is instead of rendering an empty list. Tests are on the seam rather than the symptom: profiles survive a rejected discovery, discovery still fills in when it can, and a failed overview does not go on to probe a machine it has nothing to fill in around. Plus the timeout itself, including a command that hangs. rust 685 to 688, vitest 321 to 324. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Two bugs with the same shape: Tervin knew something about itself, and told the user it was about them.
The gate blamed its own dead socket on the user's hooks
The gate panel filters on
!is_tervinso Tervin's own gate is never shown as user configuration. Every failure line still read "Tervin hook".Detection matched
--tervin-hookanywhere in the hook response, but the runtime echoes the hook's command line back only when the hook blocked. A hook that merely failed carries nothing but its own stderr. The flag was there to find whenever the gate worked and gone every time it broke, so the one moment the distinction carries weight was the one moment it was wrong.The hook client's stderr prefix is now a shared constant matched at both ends, since the two sides drifting apart is what caused this and each compiles fine on its own. Denials keep their bare text, because that stderr goes back to the agent verbatim.
A failed probe hid five configured profiles
A user with five profiles in
agents.tomlwas shown "No agent profile configured". The file was well formed andProfileConfig::load()read it correctly. The profiles were fetched and then thrown away.agents_overviewanswered two unrelated questions in one command: what the user configured (a file read) and what is installed (a subprocess per agent, ending in$SHELL -ic alias). Either half failing failed the whole command. AndCommand::output()has no timeout, so an rc file that blocks blocked the agents view behind it forever.Now two commands. The store sets the profiles before discovery is even requested, so a discovery failure lands on a screen that already shows them.
$SHELL -ic aliasis bounded at five seconds.This is the structure that makes the bug unreachable, rather than a rescue path that has to remember to run.
Tests
Both fixes are tested on the seam rather than the symptom. Notably a user's own broken hook must still be reported as theirs, which is the half that a wider
is_tervinmatch could quietly swallow.rust 682 to 688, vitest 321 to 324. clippy and fmt clean.
Not verified in the running app yet
Both were found by reading, from symptoms observed in a previous session. Neither has been confirmed against a rebuilt binary.