fix(agent): clarify validateFinalResponse error messages (#45) - #95
Draft
LukasParke wants to merge 1 commit into
Draft
fix(agent): clarify validateFinalResponse error messages (#45)#95LukasParke wants to merge 1 commit into
LukasParke wants to merge 1 commit into
Conversation
`Invalid final response: empty or invalid output` was too vague to diagnose: it read as "the model emitted a tool call and validation rejected it", which is not what the check does. `validateFinalResponse` is a pure array-length check, so a `function_call`-only output passes (test-locked in allow-final-response.test.ts). Issue #45 was a genuinely empty `output: []` misattributed to tool-call handling because the message didn't say which. Both messages now name the actual defect: - empty/invalid output distinguishes `output array is empty (length 0) for response "<id>"` — with the response id and a pointer to the strictFinalResponse/allowFinalResponse options — from `output is not an array (got <type>)` - missing required fields lists the absent field(s): id, output, or both Diagnostics only; no logic change. Both historical message prefixes are preserved verbatim so any consumer matching on them keeps working. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Perry's Review
Verdict: 💬 Comments / questions
Details
Clean diagnostics-only change. validateFinalResponse now distinguishes empty-array vs. non-array output and lists which required fields are missing, while preserving both historical message prefixes verbatim. No validation logic changed.
What I verified:
- No message-based control flow exists in the agent source — zero hits for message.includes, message.match, or message.startsWith on error strings — so the text change is safe for all consumers.
describeNonRecordalready exists and is used elsewhere; the new call site is consistent.- Both call sites of
validateFinalResponseare unaffected: the strict path still throws on empty/invalid output, and the tolerant path only setsallowEmptyOutputto true when the output is already confirmed to be an array. - The
allowEmptyOutputearly-return swallowing non-arrays is pre-existing, acknowledged in the PR description, and pinned with a test — correct call to leave it for a separate logic-change PR. - The new test file covers every diagnostic branch (empty array, non-array, missing fields, valid outputs). Two existing assertions were tightened from substring matches to anchored regexes — good.
- CI is fully green (lint, typecheck, unit-tests, e2e-tests, structural-gate).
One note: the PR is still a draft. The code looks ready to come out of draft — if you'd like a formal approval after marking it ready, let me know and I'll re-review.
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.
#45 was not reproducible
Issue #45 reported that
getResponse()throwsInvalid final response: empty or invalid outputfor tool-call-only turns. That does not happen onmain.validateFinalResponse(packages/agent/src/lib/model-result.ts) is a pure array-length check — it never inspects itemtype, so anoutputcontaining only afunction_callpasses cleanly. This is already test-locked atpackages/agent/tests/unit/allow-final-response.test.ts:370-390("does not trigger when allowFinalResponse is false" asserts the tool-call response is returned unchanged), and this PR adds a direct assertion for it too.The reporter almost certainly hit a genuinely empty
output: []. The error message just didn't say so, and the phrase "invalid output" invited the tool-call theory. This PR fixes the misleading message that caused the misdiagnosis — it changes no logic.What changed
validateFinalResponsenow names the actual defect.Empty/invalid output — distinguishes the two failure shapes:
Invalid final response: empty or invalid output — output array is empty (length 0) for response "resp_abc". The model returned no output items. This can happen when the provider returns an empty final turn; see the strictFinalResponse/allowFinalResponse optionsInvalid final response: empty or invalid output — output is not an array (got string) for response "resp_abc"Missing required fields — lists which field(s) were absent:
Invalid final response: missing required fields: idInvalid final response: missing required fields: outputInvalid final response: missing required fields: id, outputBoth historical prefixes (
Invalid final response: empty or invalid output,Invalid final response: missing required fields) are preserved verbatim as prefixes, with detail appended after an em dash, so any consumer matching on them keeps working. A doc comment on the method records that the prefixes are load-bearing.Consumer audit
Grepped
Invalid final response,empty or invalid output, andmissing required fieldsacrosssrc,tests, docs, and the Python/Go port paths:srccode does message-based control flow (grep '\.message\.includes\|\.message\.match\|\.message ===\|\.message\.startsWith' src/→ zero hits), so no retry logic depends on the exact text.packages/agent/CHANGELOG.md:82quotes the old string historically (PR fix(agent): tolerate empty final response after completed tool rounds #63) — still accurate, since it quotes the prefix.tool-terminal-empty-final.test.ts:295,312used exact-stringtoThrow(...). Both were substring matches that would have kept passing silently against the new message, so I tightened them to anchored regexes asserting the new specificity — they now verify the id and the empty-array detail rather than just the prefix.Tests (red first)
Added
packages/agent/tests/unit/final-response-validation.test.ts— 19 cases driving the private validator directly (the not-an-array and missing-field shapes are unreachable through the publiccallModelpath, since the SDK types guarantee an array).Verified red against unmodified source: 12 failed / 13 passed. Exactly the 10 new-diagnostics assertions failed with
Received: "Invalid final response: empty or invalid output", plus the 2 tightened assertions intool-terminal-empty-final.test.ts. The prefix-stability, single-line, and valid-output guards passed before the change too — confirming they lock existing behavior rather than the new text.After the fix: 61 files / 763 tests passed, no type errors.
build,typecheck,lintall green.Judgment calls
allowEmptyOutputstill swallows a non-array output. The earlyreturnfires before the message is built, so a malformed (non-array) payload is tolerated on the empty-final path — arguably wrong, since that's a broken payload rather than an empty final turn. Left as-is because this PR is diagnostics-only; pinned with an explanatory test so a future logic fix is a deliberate, visible edit.type" helper only matters for length-0 arrays, which by definition have no items. Reused the existingdescribeNonRecordhelper for the not-an-array case instead of adding a new one.Refs #45 — recommend closing the issue as not-reproducible via comment; deliberately not auto-closing from this PR since the reported bug never existed.
🤖 Generated with Claude Code