🐛 harness: log the agent's closing message and goose's error.data - #170
Conversation
Two things a dead or empty run left behind were nothing:
1. On a session/prompt failure goose keeps the JSON-RPC message at the
boilerplate ("Internal error") and puts the cause in error.data
("Error getting agent reply: Provider not set"). The harness printed
code and message only, so the pod log said `prompt error -32603:
Internal error` and the reason died with the pod. RPCError.Error()
now appends the data field and both SendPrompt and WSClient.Call
format the error with it.
2. Assistant text was collected into PromptResult.Chunks and discarded.
The live transcript lives in the tee and dies with the pod, so a run
whose only output was text ended `stage succeeded — no changes to
push` with no record of what the model said. That includes goose's
provider failures: the reply loop turns them into assistant prose
("Ran into this error: …") and ends the turn normally, so a run that
never reached the model reported success. The harness now tracks the
closing message (text after the last tool call or the latest steer,
one entry per goose message) and writes it to the pod log after the
turn — WARN plus a viewer notice when it is goose's provider-failure
text (anchored to the message: "Ran into this error", "The provider
refused this request.", "The model returned an empty response.",
"…Please resend your message to try again."), INFO otherwise,
truncated at 4000 runes. goose's status notices ("Unable to continue:
Context limit still exceeded after compaction") travel on its custom
update method and are recorded with the result too. The partial
result now survives an RPC error, and max turns is decided before the
tripping tool call so the narration that introduced it is kept.
Exit status is unchanged on purpose: a provider failure rendered as
prose still exits 0 (README says so); flipping that belongs with the
Succeeded condition work in konveyor#119.
Seen 2026-08-21: a console-created run on a Vertex gateway whose Secret
lacked GCP_PROJECT_ID died at the first prompt with a bare -32603; and a
run whose instructions asked the model a question ended Succeeded with
nothing to show — the model had answered "Round 1/10: Tell me something."
and stopped.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: ibolton336 <ibolton@redhat.com>
8ae284e to
4a3a2fc
Compare
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
A review probe showed goose can stream ordinary text and then emit its "Ran into this error…" message in the same post-tool-call segment. The per-message split covers that when chunks carry a message id (goose 1.45 stamps one on every chunk); when they do not, the failure still starts a line, so prefixes are now also matched at line starts. The WARN and the viewer notice say the call *may* have failed: a refusal is not a failed call, and the harness cannot see further than goose's text. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: ibolton336 <ibolton@redhat.com>
|
Surfacing goose's Agreed on keeping exit status out of scope here; no new issue needed since the deferred half is already tracked:
The controller half is landing in #169 (ADR 0018): it reads the container exit code ( One note for whoever picks up #118: the harness currently only does |
## Problem The plan ladder the harness publishes to viewers (rendered in the console's session panel) has three rungs. Two of them were fixed text for every run: **"Prepare workspace: clone, branch, grounding data"** and **"Agent works the stage task"**. Only the third named anything concrete (the push branch). The push tool calls viewers see as "Harness: git push (final)" did not say where they pushed. And the middle rung was one spinner for the entire agent turn, which can be minutes and hundreds of tool calls. ## Change Build the rungs from what the harness already has in hand, and keep the middle one moving. **First rung** (commit 2): repository as host and path with credentials and `.git` dropped, the branch, and the number of analysis insights written. "no analysis insights" when the fetch returned none; nothing about insights when the run has no skills and never fetched them. ``` Prepare workspace: github.com/konveyor/coolstore on branch migration-1789401169, 49 analysis insights ``` **Middle rung** (commits 1 and 3): workflow stage position when there is one, the first non-empty line of the stage instructions (falling back to the agent prompt) stripped of Markdown markers and cut to 80 runes, the model, and the turn budget. Before the prompt is sent it reads `up to 40 turns`; once running, the ladder is re-emitted on every turn with progress: ``` Stage 2 of 3 — agent works the task: “Fix the findings from the assess stage.” (gemini-2.5-pro, turn 12 of 200) ``` Stage text is rendered with parameter values substituted, so the excerpt goes through the same redactor the closing-message path uses (#170). "Turn" is the measure the harness already reports as `TurnsUsed` and compares against the native limit in `classifyOutcome`: one per `tool_call` notification. The handoff prompt continues the count from where the primary stopped. **Push titles** (commit 2): `git push to branch <branch> (final)` and `(auto-commit watcher)`. **Plumbing** (commit 3): - `SessionClient.SetTurnHandler` fires from `SendPrompt`'s loop each time the count advances; `runStage` re-emits the ladder from it. - Re-emitting per turn would fill the tee's 32-frame replay ring and evict the push and outcome frames a late viewer needs, so the ring now keys plan frames: a new ladder replaces the previous one instead of stacking. Unkeyed frames accumulate as before. - `fetchAndWriteAnalysis` returns the count it already logs. `workflowStagePosition` is split out of `isIntermediateWorkflowStage` so both read the stage metadata the same way; an index past the count now counts as invalid there too, which changes nothing for the intermediate check. ## Verification - `go build`, `go vet`, `gofmt -l` clean in `harness/`; full `go test ./...` in `harness/` passes. - New tests: the rung text across standalone/staged runs, prompt fallback, truncation, empty case, invalid stage metadata, secret masking, and progress wording with and without a budget; the prep rung's credential stripping, `.git` trimming, zero vs. unfetched insights and unparseable URL; the turn handler firing once per `tool_call` with the running count and not for other updates (demux-server test); and the replay ring keeping only the latest plan while unkeyed frames accumulate. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: ibolton336 <ibolton@redhat.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
… says why (#232) Closes #231. ## Problem goose renders a rejected model call as assistant prose and ends the turn normally: ``` Ran into this error: Server error: Failed to call Bedrock: … UnrecognizedClientException … "The security token included in the request is invalid." … ``` The harness recognised that text since #170 but only logged a WARN and told viewers to *"see the pod log"*. Exit 0, `stopReason=end_turn`, run **Succeeded** in green, Gateway **Ready** (Bedrock cannot be probed, #222). On the demo cluster today three runs died this way on a rotated AWS key and were read as a broken plan ladder, because nothing anyone could see said otherwise. ## Change **Harness** - `classifyOutcome` takes `providerError` (the existing `PromptResult.ClosingProviderError()`). With `TurnsUsed == 0` the stage is `outcomeFailed`: the model never worked, so nothing succeeded. A provider error *after* real work keeps the turn's outcome — that is #129's broader contract — but still gets the notice below. - The termination log's `stopReason` becomes `provider error: <the provider's error line>` (redacted, cut at 1000 runes). The line is the failure itself: `acp.ProviderErrorText` skips narration goose streamed ahead of it (*"Here is the plan:"* … *"Ran into this error: …"*), and `LooksLikeProviderError` is built on it, so detection and selection cannot drift. The cap is sized for #231's real line: goose renders the AWS SDK error with Debug formatting, ~500 runes with *"The security token included in the request is invalid."* past rune 160, which a 200-rune cut lost. - The viewer notice quotes the same line — *"the model provider rejected the call — no work was done: Ran into this error: … security token … is invalid."* — instead of pointing at a pod log a console user cannot open. The stage-end notice and the returned error name it too. **Controller** Unchanged. The termination blob stays opaque to the controller (ADR 0011, ADR 0018, `CONTEXT.md`): it is stored verbatim on `status.terminationData`, and `setTerminalOutcome` copies the termination message onto the `Succeeded=False` condition as before. Showing `terminationData.stopReason` beside the phase instead of the raw blob belongs in the UI, which knows the harness schema — a tackle2-ui follow-up. Net effect: the run is **Failed** instead of Succeeded, and the provider's error line — the sentence that tells the operator to rotate the Gateway credential — is in the viewer notice and in the termination blob's `stopReason`, which the console shows raw beside the phase until the UI renders it. Exit-code contract (ADR 0011) is unchanged: this is an exit-1 failure, not a new code. #170's *"exit status is unchanged on purpose … belongs with the Succeeded condition work in #119"* deferral is what this resolves; #119 landed in #169. ## Verification - `gofmt`, `go vet`, `go test ./...` green in `harness/`; `go vet` plus `TestSetTerminalOutcome`, `TestPodTerminationMessage`, `TestTerminationDataFromPod` green in `internal/controller/`. - New cases: `TestClassifyOutcome` — provider error with no turns → failed; after 7 turns → succeeded; at the native limit → limitReached still wins. `TestProviderErrorText` — the failure without the narration ahead of it, in both of goose's forms; a quoted phrase is not a failure. `TestProviderErrorSummary` — narration in the same or an earlier message, the trailer form, #231's Bedrock line kept whole, the 1000-rune cut. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: ibolton336 <ibolton@redhat.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
What
Two pod-log gaps in the harness, both found on the same day from one teammate's runs:
prompt error -32603: Internal errorwith no reason. goose puts the cause in JSON-RPCerror.data(e.g.Error getting agent reply: Provider not set); the harness parsed and dropped it.RPCError.Error()now appendsDetail()(string data unquoted, other JSON compacted) andSendPrompt/WSClient.Callwrap it with%w, so the log readsprompt error -32603: Internal error — Error getting agent reply: Provider not setand callers can
errors.Ason the*RPCError. The partialPromptResultis returned alongside the error.PromptResult.Chunksand nowhere else; the live transcript is in the tee and dies with the pod. A run whose result was text (an answer, a question back to the user, a summary) endedstage succeeded — no changes to pushwith no trace. The harness now keeps the closing message — the agent messages after the lasttool_call(or the latest human steer), one entry per goose message — and logs it after the turn (INFO, truncated at 4000 runes, secrets redacted). goose's own status notices (_goose/unstable/session/updatestatus_message/notice, e.g. Unable to continue: Context limit still exceeded after compaction) are recorded and logged too.Ran into this error…,The provider refused this request.,…Please resend your message to try again.,The model returned an empty response.) and ends the turn normally, so a run that never reached the model reported success. When the closing message is one of those (matched anchored to the message, so an agent quoting the phrase is not mistaken for a failure), it is logged as WARN and attached viewers get a notice. Credits exhaustion is not in the list: over ACP goose turns it into thesession/promptRPC error, which the first change now prints.tool_callis handled, so the narration that introduced it survives as the closing text instead of an always-empty "none".SECRET|TOKEN|PASSWORD|API_KEY|ACCESS_KEY|CREDENTIALSnames, ≥ 8 chars) plus the API key, hub token, ACP secret and git token the harness holds are replaced with[redacted]in the closing message, notices and prompt errors.Exit status is unchanged on purpose: a provider failure rendered as prose still exits 0 (README bullet updated to say so). Flipping that belongs with the
Succeededcondition work in #119.Verification
gofmt,go vet,go test ./...green. New tests:TestRPCErrorCarriesData(jsonrpc_test.go),TestSendPromptErrorIncludesGooseDetail(partial result +errors.As),TestSendPromptMaxTurnsKeepsClosingText,TestPromptResultFinalMessageFollowsLastToolCall(message boundaries, steer reset),TestPromptResultKeepsGooseNotices,TestLooksLikeProviderError(anchored, per-message), redactor tests.agent-basebuilt from this branch, runs created through the Hub API with the console's payload:agent closing message:followed by the model's text;WARN agent closing message is goose's provider-error text … ValidationException … The provided model identifier is invalid.Before: onlystage succeeded — no changes to push;TEST_API_KEY_SECRETinspec.envand instructions to print it →[redacted]in the pod log.crates/goose/src/agents/agent.rsreply loop,crates/goose/src/acp/server.rsmessage_update_meta,send_status_message_update,prompt_error_from_message_content).Context
Seen 2026-08-21: a console-created run on a Vertex gateway whose Secret lacked
GCP_PROJECT_IDdied at the first prompt with a bare-32603; and a run whose instructions asked the model a question ended Succeeded with nothing to show — the model had answered "Round 1/10: Tell me something." and stopped.🤖 Generated with Claude Code