-
Notifications
You must be signed in to change notification settings - Fork 276
fix: clear nativeArgs when tool-call finalize fails (#1221) #1634
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
canblmz1
wants to merge
2
commits into
Zoo-Code-Org:main
Choose a base branch
from
canblmz1:fix/1221-truncated-tool-args
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+210
−3
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
113 changes: 113 additions & 0 deletions
113
src/core/task/__tests__/truncated-native-tool-args.spec.ts
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| /** | ||
| * Regression test for issue #1221: truncated tool-call arguments can be silently | ||
| * written to disk. | ||
| * | ||
| * When a streamed native tool call's arguments are cut off mid-value (e.g. the | ||
| * model hits max_tokens while still writing write_to_file's `content` string), | ||
| * NativeToolCallParser.finalizeStreamingToolCall() returns null. Task.ts | ||
| * (~line 3748) reuses the same tool-use object the streaming phase was mutating | ||
| * in place and only sets `partial = false` - before the fix it left `nativeArgs` | ||
| * (built from the incomplete partial parse) untouched. | ||
| * | ||
| * presentAssistantMessage.ts (~line 443) is supposed to short-circuit exactly | ||
| * this case with a structured tool_result instead of executing the tool - but | ||
| * its guard is `isKnownTool && !block.nativeArgs && !customTool`. With | ||
| * nativeArgs still populated, the guard never fired and the truncated content | ||
| * would be passed straight to write_to_file's execution path. | ||
| * | ||
| * The fix clears `existingToolUse.nativeArgs` alongside `partial = false` at | ||
| * the finalize-null site, so the pre-existing guard actually does what its own | ||
| * comment already claimed. | ||
| */ | ||
|
|
||
| import { isValidToolName } from "../../tools/validateToolUse" | ||
| import type { ToolUse, WriteToFileToolUse } from "../../../shared/tools" | ||
|
|
||
| describe("Truncated native tool-call args on finalize failure (issue #1221)", () => { | ||
| /** | ||
| * Simulates the finalize-null branch from Task.ts (~line 3748) as it exists | ||
| * after the fix: on finalizeStreamingToolCall() returning null, mark the | ||
| * tool non-partial and clear nativeArgs. | ||
| */ | ||
| function finalizeNullBranch(existingToolUse: ToolUse): ToolUse { | ||
| existingToolUse.partial = false | ||
| existingToolUse.nativeArgs = undefined | ||
| return existingToolUse | ||
| } | ||
|
|
||
| /** | ||
| * Simulates the finalize-null branch as it existed *before* the fix, for a | ||
| * companion test proving the old behavior really was the bug (not just an | ||
| * assumption). | ||
| */ | ||
| function finalizeNullBranchBeforeFix(existingToolUse: ToolUse): ToolUse { | ||
| existingToolUse.partial = false | ||
| return existingToolUse | ||
| } | ||
|
|
||
| /** | ||
| * Simulates the short-circuit guard from presentAssistantMessage.ts (~line | ||
| * 443): `isKnownTool && !block.nativeArgs && !customTool`. Returns true when | ||
| * the tool call would be blocked (a structured tool_result emitted, no | ||
| * execution), false when it would proceed to execution. | ||
| */ | ||
| function wouldBeBlocked(block: ToolUse, customTool: unknown = undefined): boolean { | ||
| const isKnownTool = isValidToolName(String(block.name)) | ||
| return Boolean(isKnownTool && !block.nativeArgs && !customTool) | ||
| } | ||
|
|
||
| it("clears nativeArgs so a truncated write_to_file call is blocked instead of executed", () => { | ||
| // A write_to_file call whose `content` was cut off mid-stream - exactly | ||
| // the scenario in #1221. The streaming phase already populated nativeArgs | ||
| // from the incomplete partial-json parse before finalize failed. | ||
| const truncated: WriteToFileToolUse = { | ||
| type: "tool_use", | ||
| name: "write_to_file", | ||
| params: {}, | ||
| partial: true, | ||
| nativeArgs: { path: "src/config.json", content: '{"apiKey": "sk-live-abc123' /* cut off mid-string */ }, | ||
| } | ||
|
|
||
| finalizeNullBranch(truncated) | ||
|
|
||
| expect(truncated.partial).toBe(false) | ||
| expect(truncated.nativeArgs).toBeUndefined() | ||
| expect(wouldBeBlocked(truncated)).toBe(true) | ||
| }) | ||
|
|
||
| it("companion: without the fix, the same truncated call would NOT have been blocked", () => { | ||
| const truncated: WriteToFileToolUse = { | ||
| type: "tool_use", | ||
| name: "write_to_file", | ||
| params: {}, | ||
| partial: true, | ||
| nativeArgs: { path: "src/config.json", content: '{"apiKey": "sk-live-abc123' }, | ||
| } | ||
|
|
||
| finalizeNullBranchBeforeFix(truncated) | ||
|
|
||
| // This is the bug: partial is false (presented as "complete"), but | ||
| // nativeArgs still carries the truncated value, so the guard's | ||
| // `!block.nativeArgs` never becomes true and the call would proceed to | ||
| // execution with the truncated content. | ||
| expect(truncated.partial).toBe(false) | ||
| expect(truncated.nativeArgs).toEqual({ path: "src/config.json", content: '{"apiKey": "sk-live-abc123' }) | ||
| expect(wouldBeBlocked(truncated)).toBe(false) | ||
| }) | ||
|
|
||
| it("does not affect a normally-finalized (non-null) tool call", () => { | ||
| // When finalizeStreamingToolCall() succeeds, Task.ts replaces the block | ||
| // with the freshly-finalized one instead of taking this branch at all - | ||
| // this test just confirms a complete, valid nativeArgs is never touched | ||
| // by wouldBeBlocked's guard simulation. | ||
| const complete: WriteToFileToolUse = { | ||
| type: "tool_use", | ||
| name: "write_to_file", | ||
| params: {}, | ||
| partial: false, | ||
| nativeArgs: { path: "src/config.json", content: '{"apiKey": "sk-live-abc123xyz"}' }, | ||
| } | ||
|
|
||
| expect(wouldBeBlocked(complete)).toBe(false) | ||
| }) | ||
| }) | ||
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Test the production finalization path.
finalizeNullBranchduplicates the implementation instead of invokingTask.ts. These tests still pass if Line 3760 is removed or the parser-to-presenter integration changes.Drive a truncated
tool_call_partialstream through the Task flow. Assert that the native tool executor is not called and that one errortool_resultis emitted for the matching tool-use ID.🤖 Prompt for AI Agents