fix(tool): mark ToolResultBlock.error() results with state=ERROR#2186
fix(tool): mark ToolResultBlock.error() results with state=ERROR#2186logicwu0 wants to merge 1 commit into
Conversation
ToolResultBlock.error() left state unset (defaulting to RUNNING) and produced an "Error: " text prefix. determineToolResultState() only recognizes a non-RUNNING state or an "[ERROR]" text prefix, so failed tool calls (MCP timeouts, tool-not-found, execution errors, ...) fell through to SUCCESS and the agent never saw the failure. Set state=ERROR in error() so failures are classified by structured state, independent of the output text. Fixes all error() call sites at once; the output text is unchanged. Closes agentscope-ai#2157
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
oss-maintainer
left a comment
There was a problem hiding this comment.
Summary
Critical bug fix — ToolResultBlock.error() was leaving state=null (defaulting to RUNNING), causing determineToolResultState() to misclassify failures as SUCCESS.
Findings
- [Critical]
ToolResultBlock.java:207— Now explicitly setsstate = ToolResultState.ERROR. This is the correct fix. - [Info] Test
errorResultCarriesErrorState()added as regression guard. Good.
Verdict
Essential fix for tool error handling. LGTM.
Automated review by github-manager
AgentScopeJavaBot
left a comment
There was a problem hiding this comment.
🤖 AI Review
This is a clean, well-targeted bug fix for issue #2157. The root cause was a mismatch between ToolResultBlock.error() (which produced "Error: " text prefix with state defaulting to RUNNING) and ReActAgent.determineToolResultState() (which checks for non-RUNNING state or "[ERROR]" text prefix). Neither condition matched, so all tool failures — MCP timeouts, tool-not-found, execution errors — were silently classified as SUCCESS. The fix correctly sets state = ToolResultState.ERROR in the factory method, decoupling error identification from fragile text-prefix heuristics. The change is minimal (+21/-2), backward-compatible (text output unchanged), and includes both an extended integration assertion and a focused regression test. Full test suite reportedly green (2210 passed, 0 failed).
One minor follow-up consideration (outside this PR's scope): ReActAgent.buildErrorToolResult() (line ~1753) still constructs error results via the builder without setting state, relying on the "[ERROR]" text prefix heuristic. Applying the same structured-state approach there would improve consistency and allow eventual deprecation of the text-prefix fallback.
| // determineToolResultState() misclassified failures as SUCCESS. | ||
| ToolResultBlock result = ToolResultBlock.error("boom"); | ||
| assertEquals(ToolResultState.ERROR, result.getState()); | ||
| } |
There was a problem hiding this comment.
[praise] Excellent regression guard with a clear historical comment explaining why the bug existed (state defaulted to RUNNING, text prefix was "Error: " not "[ERROR]"). This level of context in a test comment is exactly what future maintainers need to understand the intent without digging through git history.
| * @return ToolResultBlock with error output | ||
| * @return ToolResultBlock with error output and {@code state = ERROR} | ||
| */ | ||
| public static ToolResultBlock error(String errorMessage) { |
There was a problem hiding this comment.
[praise] Clean fix — setting the structured state field directly in the factory method is the right approach. It fixes all error call sites (MCP timeouts, tool-not-found, generic failures) in one place and removes the dependency on the brittle "[ERROR]" text-prefix heuristic in determineToolResultState().
AgentScopeJavaBot
left a comment
There was a problem hiding this comment.
🤖 AI Review
This is a clean, well-targeted bug fix for issue #2157. The root cause was a mismatch between ToolResultBlock.error() (which produced "Error: " text prefix with state defaulting to RUNNING) and ReActAgent.determineToolResultState() (which checks for non-RUNNING state or "[ERROR]" text prefix). Neither condition matched, so all tool failures — MCP timeouts, tool-not-found, execution errors — were silently classified as SUCCESS. The fix correctly sets state = ToolResultState.ERROR in the factory method, decoupling error identification from fragile text-prefix heuristics. The change is minimal (+21/-2), backward-compatible (text output unchanged), and includes both an extended integration assertion and a focused regression test. Full test suite reportedly green (2210 passed, 0 failed).
One minor follow-up consideration (outside this PR's scope): ReActAgent.buildErrorToolResult() (line ~1753) still constructs error results via the builder without setting state, relying on the "[ERROR]" text prefix heuristic. Applying the same structured-state approach there would improve consistency and allow eventual deprecation of the text-prefix fallback.
| // determineToolResultState() misclassified failures as SUCCESS. | ||
| ToolResultBlock result = ToolResultBlock.error("boom"); | ||
| assertEquals(ToolResultState.ERROR, result.getState()); | ||
| } |
There was a problem hiding this comment.
[praise] Excellent regression guard with a clear historical comment explaining why the bug existed (state defaulted to RUNNING, text prefix was "Error: " not "[ERROR]"). This level of context in a test comment is exactly what future maintainers need to understand the intent without digging through git history.
| * @return ToolResultBlock with error output | ||
| * @return ToolResultBlock with error output and {@code state = ERROR} | ||
| */ | ||
| public static ToolResultBlock error(String errorMessage) { |
There was a problem hiding this comment.
[praise] Clean fix — setting the structured state field directly in the factory method is the right approach. It fixes all error call sites (MCP timeouts, tool-not-found, generic failures) in one place and removes the dependency on the brittle "[ERROR]" text-prefix heuristic in determineToolResultState().
What
ToolResultBlock.error()returned a result whosestatewas left unset (theconstructor defaults it to
RUNNING) with an"Error: "text prefix. ButReActAgent.determineToolResultState()only classifies a result asERRORwhen either the structured state is a non-
RUNNINGvalue, or the output textstarts with
"[ERROR]". Neither held, so failed tool calls fell through to thedefault
SUCCESSbranch — the agent never saw the failure.This affected every
error()call site: MCP timeouts, tool-not-found, emptyresults, and generic execution failures.
Reproduced in the issue with an MCP tool timing out after 3 retries:
Fix
Set
state = ToolResultState.ERRORinToolResultBlock.error(), so failures areclassified by structured state, decoupled from the fragile text-prefix heuristic.
This fixes all error paths at once. The output text (
"Error: ...") isunchanged, so existing behavior and assertions are preserved.
Tests
shouldReturnErrorWhenToolThrowsto assert the failing-tool responsecarries
state == ERROR.errorResultCarriesErrorStateas a focused regression guard.agentscope-coresuite: 2210 passed, 0 failed (9 skipped).Closes #2157