fix: Complete tool call handling fixes for SDK mode and conversation history#26
Closed
saxyguy81 wants to merge 5 commits intoCaddyGlow:mainfrom
Closed
fix: Complete tool call handling fixes for SDK mode and conversation history#26saxyguy81 wants to merge 5 commits intoCaddyGlow:mainfrom
saxyguy81 wants to merge 5 commits intoCaddyGlow:mainfrom
Conversation
added 5 commits
December 18, 2025 23:31
Tool calls were only being yielded for SSE format, not dict format (used by SDK mode). This caused SDK mode clients to miss tool call responses entirely. Changes: - Remove SSE-only condition from tool call yielding - Add proper tool call indexing for multiple simultaneous calls - Clear tool_calls dict after yielding to prevent duplicates - Add debug logging for tool call processing 🤖 Generated with [Claude Code](https://claude.ai/code)
Fixes multiple race conditions affecting fast STDIO MCP tools: 1. Message discarding race (stream_worker.py) - Removed separate has_listeners() check - Always call broadcast() which handles atomically 2. Listener setup race (stream_handle.py) - Split worker lifecycle into create/start phases - Pre-register listener before starting worker 3. Interrupt timing issues (stream_handle.py) - Added 100ms drain period before interrupt - Changed asyncio.create_task() to await These race conditions were particularly problematic for STDIO tools (filesystem, chrome-extension) which respond in <1ms, hitting the race windows that slower API calls don't. 🤖 Generated with [Claude Code](https://claude.ai/code)
Adds error handling and completion tracking around yield statements in the SDK streaming processor: - Added try-except for GeneratorExit at 5 locations - Added completion tracking (chunks_sent/total_chunks) - Added logging for interrupted blocks with completion ratio - Ensures partial block delivery is tracked and logged This helps diagnose issues when clients disconnect mid-stream and ensures proper cleanup of streaming resources. 🤖 Generated with [Claude Code](https://claude.ai/code)
Adds _sanitize_tool_results() to handle cases where conversation history is compacted and tool_result blocks become orphaned (their matching tool_use blocks are removed). The Anthropic API requires each tool_result to have a matching tool_use in the immediately preceding assistant message. When clients compact history, this invariant can be violated. Fix: - Scan for valid tool_use IDs in preceding assistant message - Remove orphaned tool_result blocks that don't match - Convert orphaned results to text blocks to preserve information - Log warnings when sanitization occurs Error fixed: "unexpected tool_use_id found in tool_result blocks" 🤖 Generated with [Claude Code](https://claude.ai/code)
Adds comprehensive test coverage for the tool call fixes: 1. test_tool_call_streaming_fix.py - Tests tool calls work in dict format (SDK mode) - Tests multiple tool calls are properly indexed - Tests SSE format still works (no regression) - Tests complex tool call arguments 2. test_tool_result_sanitization.py (17 test cases) - Tests valid tool_results are preserved - Tests orphaned tool_results are removed - Tests mixed valid/orphaned scenarios - Tests conversation compaction scenario - Tests edge cases (empty, complex content, etc.) 🤖 Generated with [Claude Code](https://claude.ai/code)
Owner
|
Hello, did you try v0.2 first? https://github.com/CaddyGlow/ccproxy-api/tree/dev/v0.2 I have to switch main to this version but didn't get proper time to finish cleaning up the log output. It should be ready to use though. I put a lot of effort into improving the handling of transformations between APIs. I'll happily apply any fixes to this branch.
|
Author
|
This PR has been superseded by a new PR targeting |
c982f22 to
e8d882b
Compare
Owner
|
Fix in v0.2.0 |
Author
|
Superseded by the dev/v0.2 PR: #31 (merged). |
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.
Real-World Use Case
I'm using ccproxy-api to route requests from the superdesign.dev VSCode plugin to my Claude Max subscription. This setup works great for simple conversations, but breaks on longer conversations that trigger tool calls, especially after the client implicitly compacts the conversation history.
Symptoms I Encountered
"unexpected tool_use_id found in tool_result blocks"Root Causes Found
After deep investigation, I found 6 bugs in the tool call handling code:
Summary
This PR fixes all the above bugs affecting tool call handling in ccproxy-api, particularly when using SDK mode with STDIO MCP tools and when conversation history contains compacted/summarized messages.
Issues Fixed:
tool_resultblocks causing API errors after conversation compactionChanges
1. OpenAI Streaming Adapter (
adapters/openai/streaming.py)Bug: Tool calls were only yielded for SSE format, not dict format (SDK mode).
2. SDK Streaming Race Conditions (
claude_sdk/stream_worker.py,stream_handle.py)Bug #1: Non-atomic check-and-broadcast caused message loss
has_listeners()checkbroadcast()which handles atomicallyBug #2: Worker started before listener was registered
Bug #3: Fire-and-forget cleanup lost in-flight messages
asyncio.create_task()toawait3. SDK Streaming Yield Guarantees (
claude_sdk/streaming.py)Bug: No error handling around yields
4. Tool Result Sanitization (
adapters/openai/adapter.py)Bug: Orphaned
tool_resultblocks caused API errors when conversation history was compacted.Error:
"unexpected tool_use_id found in tool_result blocks"_sanitize_tool_results()methodWhy These Bugs Occurred
tool_useblocks while keeping user messages withtool_resultreferencesTest Plan
New Test Files
tests/test_tool_call_streaming_fix.py- OpenAI adapter tool call teststests/test_tool_result_sanitization.py- Orphaned tool_result tests (17 test cases)Run Tests
# Tool result sanitization (standalone) python -m pytest tests/test_tool_result_sanitization.py --noconftest -c /dev/null -vManual Testing Done
Files Changed
ccproxy/adapters/openai/streaming.pyccproxy/adapters/openai/adapter.pyccproxy/claude_sdk/stream_worker.pyccproxy/claude_sdk/stream_handle.pyccproxy/claude_sdk/streaming.pyBreaking Changes
None. All changes are backward compatible.
🤖 Generated with Claude Code