You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(agent): finish the TextGenError migration across every path and consumer
Closes out the remaining gaps rather than deferring them, so this PR lands
complete. The unification previously stopped at the explain + non-streaming
GenerateText boundary; everything else fell through to the generic
"Failed to generate summary" branch with no classification and no remediation.
**Streaming path now classifies (the one with real user impact).**
TextGeneratorAdapter prefers streaming, so GenerateTextStreaming is the path
`explain --generate` actually takes for Claude — and nine of its ten failure
returns were plain fmt.Errorf. A stale key gave:
claude stream failed: Invalid API key · Please run /login: exit status 2
and now gives:
Claude authentication failed
message: Invalid API key · Please run /login
try: run `claude login` and retry
All ten returns route through a shared streamFailure() that applies the same
stderr classification as generate.go (HTTP status, then auth phrase) and
attaches evidence. cmd.Start failures classify as CLIMissing via
IsExecNotFoundErr, so a missing binary reads "not installed or not on PATH"
instead of a raw exec error.
**External agents return *TextGenError.** They are selectable summary
providers, so without this the type's own doc claim ("every summary provider")
was false and their failures missed errors.As entirely.
**Pi is wired and covered.** PiAgent gained a CommandRunner field, so pi joins
TestGenerateText_Matrix — it previously had zero coverage of any failure kind.
Separately, pi/models.go no longer routes --list-models through
HandleTextGenResult: that builds the summary error surface, so a model-listing
failure could render as "Pi failed to generate the summary".
**Registry gaps now fail a test instead of degrading silently.**
TestSummaryProviderTablesAreComplete enumerates SummaryCapableAgents() (newly
exported) and asserts displayNameFor, syntheticFallback and
summaryProviderBinaries cover all six. Forgetting one previously shipped a raw
registry key in the label ("copilot-cli authentication failed"), a missing try
row, or an agent silently absent from the picker — none of which failed
anything. Also dropped //nolint:exhaustive from kindPrefix so a sixth Kind
fails lint where it has to be handled.
**Classifier completed.** ClassifyStderrHTTPStatus now maps every 4xx (matching
classifyEnvelopeFields' >=400 && <500 arm — 413/422 previously fell to Unknown
on one path and Config on the other), treats 402 as rate-limit rather than
config since credit exhaustion is a "wait or top up" problem, and matches the
parenthesized form ("Unauthorized (401)", "rate limit exceeded (429)").
Precedence changed from positional to specificity: Auth > RateLimit > Config.
Completing the 4xx mapping made "first recognized wins" degenerate to "first
wins", which let a leading 413 mask a later 401 — my own regression test caught
it. Auth and RateLimit carry specific remediations; Config is the catch-all, so
the actionable kind should win wherever it appears.
**Privacy.** manual_commit_condensation.go logged err.Error() verbatim, and
Message can be provider stdout — the model's prose summary of the user's
transcript — since stdout is the fallback for stdout-primary CLIs. Now logs
kind/provider/api_status/exit_code/message_len instead. CLAUDE.md forbids
logging user content.
**Hygiene:** removed the unreachable double-parse branch in GenerateText (the
only failure return not wrapped in withEvidence), and dropped a vacuous
errors.Is(err, context.Canceled) assertion in the streaming test that used
context.Background() and could never fire.
Verified: 8572 unit tests, 448 integration tests, lint 0 issues on a cleaned
cache.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
returnagent.HandleTextGenResult(res, runErr, agent.AgentNamePi, "pi CLI returned empty output", nil) //nolint:wrapcheck // return unwrapped: the explain layer renders label+message from the typed error, so a wrap prefix would leak into user output. errors.As (*TextGenError) / errors.Is (ctx sentinel) must reach it unflattened.
0 commit comments