Repository navigation
Description
Observed Behavior
agent-framework-anthropic drops redacted_thinking content blocks when it parses a Claude response. When the function-invocation loop sends the assistant turn back with the tool_result, the redacted_thinking block is missing. The tool_use block, and any normal thinking block, are replayed correctly.
In _parse_contents_from_anthropic, redacted_thinking falls through to case _. That branch adds the type to request_state.dropped_content_types, logs at debug level, and returns no Content. Only the pause_turn continuation reads dropped_content_types, since #9718. The normal tool-call loop doesn't read it, so the block is lost silently.
Expected Behavior
redacted_thinking should round-trip unchanged, the same way thinking and its signature already do. Anthropic requires the thinking blocks of the last assistant turn, including redacted_thinking, to be passed back unmodified while tool use continues (Preserving thinking blocks).
The expected replay for [redacted_thinking, tool_use] is [{"type": "redacted_thinking", "data": "<opaque>"}, {"type": "tool_use", ...}].
Steps to Reproduce
- Install the packages listed under Package Versions.
- Run the script below. It is offline: it uses synthetic SDK blocks, makes no network call and needs no credentials.
- The control assertion (plain
thinking) passes. Theredacted_thinkingassertion fails.
Minimal Reproduction
import json
from agent_framework import Message
from agent_framework_anthropic import AnthropicClient
from anthropic.types.beta import BetaRedactedThinkingBlock, BetaThinkingBlock, BetaToolUseBlock
client = AnthropicClient(api_key="unused-offline-test", model="claude-haiku-4-5")
def replay(blocks):
# Real provider parsing and replay serialization; no network call.
contents = client._parse_contents_from_anthropic(blocks)
message = Message(role="assistant", contents=contents)
return [b["type"] for b in client._prepare_message_for_anthropic(message)["content"]]
tool_use = BetaToolUseBlock(type="tool_use", id="toolu_test", name="lookup", input={})
thinking = BetaThinkingBlock(type="thinking", thinking="Visible summary.", signature="synthetic-signature")
redacted = BetaRedactedThinkingBlock(type="redacted_thinking", data="synthetic-opaque-data")
print(json.dumps({"thinking": replay([thinking, tool_use]), "redacted_thinking": replay([redacted, tool_use])}))
assert replay([thinking, tool_use]) == ["thinking", "tool_use"]
assert replay([redacted, tool_use]) == ["redacted_thinking", "tool_use"] # FailsError Messages and Stack Traces
{"thinking": ["thinking", "tool_use"], "redacted_thinking": ["tool_use"]}
Traceback (most recent call last):
File "maf_redacted_repro.py", line 23, in <module>
assert replay([redacted, tool_use]) == ["redacted_thinking", "tool_use"] # Fails
AssertionError
This report is about silent block loss in MAF's request building. I have not reproduced the resulting API error against live Claude, because redacted thinking only appears when Claude's safety systems flag the reasoning. I also ran an end-to-end check against a local mock of the Messages API that enforces the documented rule. In that check, `Agent.run` with a tool and extended thinking fails on the second request with `ChatClientInvalidRequestException` (400), in both streaming and non-streaming runs and through `Agent.as_tool`.
Package Versions
agent-framework-core==1.24.0, agent-framework-anthropic==1.0.0b261030, anthropic==1.17.0
Python Version
Python 3.13.12
Operating System
Windows
Regression
No
Additional Context
The behavior is still the same on main at 708c8f3a59bb139e532b609c344bff2a108672d1 (from reading the source):
_parse_contents_from_anthropicdefault case drops the block.test_parse_contents_records_dropped_redacted_thinkingasserts the drop, but only to guardpause_turnreplay.
Related, all closed: #5783 and #8168 cover thinking blocks lost before replay; #9718 added the dropped_content_types guard for pause_turn.
A possible fix, following how #9718 keeps server-tool blocks: parse redacted_thinking into a reasoning Content that carries data (for example in additional_properties["anthropic"]), and serialize it back as {"type": "redacted_thinking", "data": ...} in _prepare_message_for_anthropic. The orphan-signature skip must not drop it, and streaming aggregation must keep it as its own block. Regression coverage: round-trip [redacted_thinking, tool_use] and [thinking, redacted_thinking, tool_use], both streaming and non-streaming. If a block still can't be replayed, failing loudly before the request is better than dropping it silently.
Acknowledgements
- I searched existing issues and did not find a duplicate.
- I personally verified this behavior and the reproduction details are authentic.
- I will wait for explicit maintainer agreement before starting implementation of a non-trivial change.
Metadata
Metadata
Labels
Type
Projects
- StatusShow more project fieldsDone
Activity
github-actions commentedon Nov 4, 2026
🤖 Automated triage reproduction notes (agent-authored — trust but verify)
Agent analysis
Repro:
python/packages/anthropic/agent_framework_anthropic/_chat_client.py::_parse_contents_from_anthropicat lines 2102-2104 dropsredacted_thinkingthrough its default branch when it precedes a tool call. Minimal repro: parse syntheticBetaRedactedThinkingBlockandBetaToolUseBlockinstances, wrap the resulting contents in an assistantMessage, then call_prepare_message_for_anthropic; the output contains onlytool_use.python/packages/anthropic/tests/test_anthropic_client.py::test_redacted_thinking_round_trips_with_tool_use1.0.0b2610301.0.0b261030Python: Replay Anthropic redacted_thinking blocks in tool loops
Python: Replay Anthropic redacted_thinking blocks in tool loops (micr…