Repository navigation
Python: [Bug]: Anthropic response stream is not closed on early tool-call validation failure #9480
Description
Observed Behavior
When the Anthropic client rejects an invalid streamed local tool call, it raises ResponseInvalidatedException without closing the underlying HTTP response body.
The tool is correctly not executed. The defect reported here is response cleanup on early validation failure.
Expected Behavior
Close the response stream when validation exits early, while preserving the exception and preventing tool execution.
Steps to Reproduce
Install:
pip install agent-framework-core==1.22.0 agent-framework-anthropic==1.0.0b261015 anthropic==1.11.0 httpx2==2.13.1Run the standalone script below. It uses stock MAF and an instrumented mock response body; no credentials, network, application code, or custom middleware are needed.
Minimal Reproduction
import asyncio
import json
import httpx2 as httpx
from anthropic import AsyncAnthropic
from agent_framework import tool
from agent_framework.anthropic import AnthropicClient
from agent_framework.exceptions import ResponseInvalidatedException
async def main():
closed, executed = [], []
@tool
def lookup() -> str:
"""Return a test value."""
executed.append(True)
return "ok"
# Deliberately malformed: a local tool call ends with end_turn, not tool_use.
events = [
{"type": "message_start", "message": {
"id": "msg_test", "type": "message", "role": "assistant",
"model": "claude-sonnet-4-6", "content": [], "stop_reason": None,
"stop_sequence": None, "usage": {"input_tokens": 1, "output_tokens": 1},
}},
{"type": "content_block_start", "index": 0, "content_block": {
"type": "tool_use", "id": "call_test", "name": "lookup", "input": {},
}},
{"type": "content_block_stop", "index": 0},
{"type": "message_delta", "delta": {
"stop_reason": "end_turn", "stop_sequence": None,
}, "usage": {"output_tokens": 2}},
{"type": "message_stop"},
]
class Body(httpx.AsyncByteStream):
async def __aiter__(self):
for event in events:
yield f"event: {event['type']}\ndata: {json.dumps(event)}\n\n".encode()
async def aclose(self):
closed.append(True)
def transport(request):
return httpx.Response(
200, headers={"content-type": "text/event-stream"}, stream=Body(),
)
async with httpx.AsyncClient(transport=httpx.MockTransport(transport)) as http:
async with AsyncAnthropic(api_key="unused-offline-test", http_client=http) as raw:
client = AnthropicClient(anthropic_client=raw, model="claude-sonnet-4-6")
try:
await client.get_response(
"Use lookup", stream=True,
options={"tools": [lookup], "max_tokens": 512},
).get_final_response()
except ResponseInvalidatedException:
print("ResponseInvalidatedException raised")
print(f"tool_executed={bool(executed)}, body_closed={bool(closed)}")
print(f"body_closed_after_client_exit={bool(closed)}")
asyncio.run(main())Actual Output
ResponseInvalidatedException raised
tool_executed=False, body_closed=False
body_closed_after_client_exit=False
Expected: the exception still occurs, the tool is not executed, and the body is closed.
Environment
agent-framework-core==1.22.0agent-framework-anthropic==1.0.0b261015anthropic==1.11.0httpx2==2.13.1- Python 3.14.7, macOS
- Regression: unknown
Additional Context
The streaming branch of _inner_get_response iterates the stream returned by beta.messages.create(..., stream=True) and can raise during validation without a context manager or a finally block closing that stream (source).
Also reproduced using stock AnthropicFoundryClient. Similar early rejection occurs with an unclosed local tool block.
Scope: this is a deterministic cleanup reproduction using deliberately malformed provider events. It does not establish how often such events occur with the real service, a production socket leak, or worker crashes. Normal successful responses are not the case reported here.
Metadata
Metadata
Labels
Type
Projects
- StatusShow more project fieldsDone
Activity
github-actions commentedon Oct 18, 2026
🤖 Automated triage reproduction notes (agent-authored — trust but verify)
Agent analysis
Repro:
python/packages/anthropic/agent_framework_anthropic/_chat_client.py::RawAnthropicClient._inner_get_responsearound lines 617-672 leaves the provider response body open when a streamed local tool call terminates with an invalid stop reason. The trigger is a closedtool_useblock followed byend_turnandmessage_stop. Runtest_streaming_local_tool_call_invalidation_closes_response_bodywith its instrumentedhttpx2.AsyncByteStream; the exception is preserved and the tool is not executed, butclosedremains empty.python/packages/anthropic/tests/test_anthropic_client.py::test_streaming_local_tool_call_invalidation_closes_response_body1.0.0b2610151.0.0b261015Epsilon006 commentedon Oct 18, 2026
Hi, I'd be happy to work on this issue.
I noticed that the automated triage has reproduced the stream cleanup failure and that the issue is assigned to Eduard van Valkenburg (@eavanvalkenburg) .
Is this open for external contribution, or is a fix already in progress?
If it's available, I can add a focused offline regression test and implement a minimal fix to ensure the response stream is properly closed on validation failure, while preserving exception propagation and preventing tool execution.
Happy to coordinate to avoid duplicating work. Thanks!
eavanvalkenburg commentedon Oct 26, 2026
/plan Correct per-request Anthropic response-stream ownership on early validation failure. Current main directly iterates beta.messages.create(stream=True) and can raise ResponseInvalidatedException without deterministic response closure. Use the supported SDK stream/context lifecycle, preserving the original invalidation or caller cancellation and zero local tool execution; define cleanup-error precedence rather than masking the primary failure. Close the individual response, not an injected caller-owned SDK/HTTP client. Cover normal completion, invalid terminal tool sequences, provider errors, cancellation and explicit early consumer closure with an instrumented offline response body; verify inherited Foundry/Bedrock/Vertex paths as applicable. Distinguish provider HTTP-body ownership from the related AgentLoop inner-stream work in #9331 and reuse any applicable existing lifecycle helper rather than starting overlapping work. Follow spec 004 for affected history/streaming/provider validation. The report demonstrates deterministic mock-body nonclosure, not a proven production socket leak or service frequency. Coordinate with volunteer Alpinist (@Epsilon006) after core/provider owners agree on scope; no ready label or new assignment while planning is pending.