Skip to content

Conversation

@daniel-lxs
Copy link
Member

@daniel-lxs daniel-lxs commented Dec 2, 2025

Summary

This PR fixes a regression introduced in PR #9542 that causes malformed tool calls to hang indefinitely for both native and XML protocols.

Root Cause Analysis

The Regression in PR #9542

BEFORE PR #9542 (commit cad6145):

if (partialBlocks.length > 0) {
    presentAssistantMessage(this)
}

AFTER PR #9542 (commit 0327f12):

if (partialBlocks.length > 0 && partialBlocks.some((block) => block.type !== "tool_use")) {
    presentAssistantMessage(this)
}

PR #9542 added the condition && partialBlocks.some((block) => block.type !== "tool_use") thinking it would optimize by not re-presenting native tool blocks that were already presented during streaming.

But this breaks XML protocol because:

  1. XML protocol parses tool_use blocks from the text stream (they don't come via native events)
  2. When only a malformed tool_use is the partial block (no text blocks), the new condition evaluates to false
  3. presentAssistantMessage is never called
  4. The tool never executes → hang

Native Protocol Issue

For native protocol, PR #9542 also introduced streaming tool calls (tool_call_partial events) with three phases:

  • tool_call_start - creates a partial tool use
  • tool_call_delta - accumulates argument chunks
  • tool_call_end - calls finalizeStreamingToolCall() to complete it

When a model sends malformed arguments (e.g., {"path": "file.go"} instead of the required structure), finalizeStreamingToolCall() returns null and the tool was silently ignored, leaving a partial tool use that would hang.

The Fix

  1. Remove the restrictive condition: Restore the original behavior that calls presentAssistantMessage for ALL partial blocks, including tool_use-only scenarios

  2. Handle malformed native tool calls: When finalizeStreamingToolCall() returns null, we now mark the existing partial tool as complete (partial: false) so it proceeds to validation which catches the missing parameters

Testing

  • All existing tests pass
  • Added tests for malformed native tool calls with missing/incomplete arguments

Important

Fixes hanging issue for malformed tool calls in native and XML protocols by adjusting tool call handling in Task.ts.

  • Behavior:
    • Fixes hanging issue for malformed tool calls in both native and XML protocols by removing restrictive condition in Task.ts.
    • Ensures presentAssistantMessage is called for all partial blocks, including tool_use-only scenarios.
  • Tool Call Handling:
    • In Task.ts, when finalizeStreamingToolCall() returns null, marks tool as complete (partial: false) to proceed to validation.
    • Cleans up tracking and ensures tool call is presented for validation of missing parameters.
  • Testing:
    • All existing tests pass.
    • Added tests for malformed native tool calls with missing/incomplete arguments.

This description was created by Ellipsis for 52b890e. You can customize this summary. It will automatically update as commits are pushed.

When a model sends a malformed tool call (e.g., apply_diff with only
{"path": "file.go"} and missing required parameters), the
NativeToolCallParser.finalizeStreamingToolCall() returns null because
the JSON is incomplete or can't be parsed properly.

Previously, when finalizeStreamingToolCall returned null, the code did
nothing - leaving the partial tool use in assistantMessageContent with
partial: true. This caused the tool to never be executed and no
tool_result was sent back to the API, causing the system to hang.

This fix ensures that when finalizeStreamingToolCall returns null, we
still mark the existing partial tool use as complete (partial: false)
so it gets executed. The tool's validation will catch missing required
parameters and return a proper error message to the model.

Fixes #9542
@daniel-lxs daniel-lxs requested review from cte, jr and mrubens as code owners December 2, 2025 21:57
@dosubot dosubot bot added size:L This PR changes 100-499 lines, ignoring generated files. bug Something isn't working labels Dec 2, 2025
@roomote
Copy link
Contributor

roomote bot commented Dec 2, 2025

Rooviewer Clock   See task on Roo Cloud

Review completed. All previously flagged issues have been resolved.

The latest commit removes the problematic test code that was flagged in previous reviews. No new issues found.

Previous reviews

Mention @roomote in a comment to request specific changes to this pull request or fix all unresolved issues.

@hannesrudolph hannesrudolph added the Issue/PR - Triage New issue. Needs quick review to confirm validity and assign labels. label Dec 2, 2025
@daniel-lxs daniel-lxs moved this from Triage to PR [Needs Review] in Roo Code Roadmap Dec 2, 2025
@hannesrudolph hannesrudolph added PR - Needs Review and removed Issue/PR - Triage New issue. Needs quick review to confirm validity and assign labels. labels Dec 2, 2025
})
})

describe("malformed tool calls", () => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests seem to pass with the Task.ts changes reverted as well

PR #9542 added a condition that only called presentAssistantMessage()
if there was at least one non-tool_use partial block. This broke XML
protocol because XML tools are parsed from the text stream (not via
native events), and when only a malformed tool_use was the partial
block, presentAssistantMessage was never called, causing a hang.

This commit restores the original behavior that works for both protocols.
These tests verify that presentAssistantMessage is called at stream end
even when only tool_use blocks are partial. The tests explicitly show
the difference between the fixed condition and the broken condition
that was introduced in PR #9542.
@daniel-lxs daniel-lxs force-pushed the fix/malformed-native-tool-call-hang branch from 643449e to d942014 Compare December 2, 2025 23:00
// For XML protocol: includes both text and tool_use blocks parsed from the text stream
// For native protocol: tool_use blocks were already presented during streaming via
// tool_call_partial events, but we still need to present them if they exist (e.g., malformed)
if (partialBlocks.length > 0) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this change fixes XML and the other one fixes native tool calls?

})
})

describe("Partial tool_use blocks at stream end", () => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this test also passes before and after the Task.ts changes

@daniel-lxs daniel-lxs marked this pull request as draft December 2, 2025 23:45
@daniel-lxs daniel-lxs marked this pull request as ready for review December 2, 2025 23:54
expect(correctCondition).not.toBe(brokenCondition)
})

it("ACTUAL CODE TEST: Task.ts must NOT filter out tool_use blocks when calling presentAssistantMessage", async () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test that reads Task.ts source and checks for a specific condition relies on exact string matching of source formatting, which is brittle. Consider refactoring to test behavior rather than text formatting.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I’d probably remove this one

@github-actions github-actions bot mentioned this pull request Dec 3, 2025
daniel-lxs added a commit that referenced this pull request Dec 3, 2025
Adds a test that reads the actual Task.ts source code and verifies that the
broken condition (filtering out tool_use blocks) is not present, preventing
future regressions of #9758
mini2s added a commit to zgsm-ai/costrict that referenced this pull request Dec 3, 2025