Skip to content

Stop Tervin reporting its own failures as the user's configuration - #33

Merged
QuintinBotes merged 2 commits into
mainfrom
tervin-self-reporting
Aug 3, 2026
Merged

Stop Tervin reporting its own failures as the user's configuration#33
QuintinBotes merged 2 commits into
mainfrom
tervin-self-reporting

Conversation

@QuintinBotes

Copy link
Copy Markdown
Owner

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_tervin so Tervin's own gate is never shown as user configuration. Every failure line still read "Tervin hook".

Detection matched --tervin-hook anywhere 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.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 (a file read) and what is installed (a subprocess per agent, ending in $SHELL -ic alias). Either half failing failed the whole command. And Command::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 alias is 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_tervin match 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.

QuintinBotes and others added 2 commits August 3, 2026 01:27
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>
@QuintinBotes
QuintinBotes merged commit 70b70c7 into main Aug 3, 2026
3 checks passed
@QuintinBotes
QuintinBotes deleted the tervin-self-reporting branch August 3, 2026 15:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant