Skip to content

fix: drop thinking blocks without valid signatures instead of sending signature=None - #82

Open
Dan Shapiro (danshapiro) wants to merge 1 commit into
microsoft:mainfrom
danshapiro:fix/drop-unsigned-thinking-blocks
Open

fix: drop thinking blocks without valid signatures instead of sending signature=None#82
Dan Shapiro (danshapiro) wants to merge 1 commit into
microsoft:mainfrom
danshapiro:fix/drop-unsigned-thinking-blocks

Conversation

@danshapiro

Copy link
Copy Markdown

Fixes microsoft/amplifier#330

Root cause

Sessions that run partly on non-Anthropic providers (e.g. OpenAI reasoning turns under a mixed-provider routing matrix) persist canonical ThinkingBlocks with signature: None (or no signature field at all — OpenAI reasoning blocks use encrypted content payloads instead of Anthropic's cryptographic signature). On resume, this provider's history serialization copied the signature through as-is, so the request was sent with signature: null and Anthropic rejected it with HTTP 400:

messages.N.content.0.thinking.signature.str: Input should be a valid string

Because the same persisted history is replayed on every resume, the session is permanently bricked.

Fix

Anthropic requires a valid non-empty signature to accept a thinking block as input — the field cannot simply be omitted — so unsigned thinking blocks are now dropped entirely:

  • _clean_content_block(): returns None for a thinking block whose signature is not a non-empty string (valid blocks pass through unchanged).
  • _convert_messages() (all three call sites — thinking_block + tool_calls path, thinking_block-only path, structured-content path): skips dropped blocks. If an assistant message loses ALL of its content this way, a minimal placeholder text block ((internal reasoning omitted)) is emitted instead of an empty content array, which Anthropic also rejects.
  • _convert_to_chat_response(): no longer persists response thinking blocks without a valid signature (getattr(block, "signature", None) previously flowed None into canonical history), so unsigned blocks cannot re-enter the transcript from this side either.

Evidence / prevalence

Tests

New tests/test_thinking_signature_guard.py (10 tests) covering:

  • valid string signature → passed through unchanged
  • signature: None → dropped, sibling text/tool_use blocks preserved
  • missing signature field → dropped
  • empty-string signature → dropped
  • assistant message where ALL content was unsigned thinking → placeholder emitted, never an empty content array (both thinking_block and structured-content paths)
  • _convert_to_chat_response skips unsigned / keeps signed thinking blocks

Full suite: 585 passed (uv run --with amplifier-core pytest). ruff format --check clean; ruff check introduces no new findings vs. main.


Generated with Amplifier

… signature=None

Sessions that run partly on non-Anthropic providers (e.g. OpenAI reasoning
turns in a mixed-provider routing setup) persist thinking blocks with
signature=None or no signature field. The Anthropic history serialization
copied the signature through as-is, so every resume sent signature=None and
the API rejected the request with HTTP 400
(messages.N.content.0.thinking.signature.str: Input should be a valid
string), permanently bricking the session.

Anthropic requires a valid non-empty signature to accept a thinking block
as input -- the field cannot simply be omitted -- so unsigned blocks are now
dropped entirely:

- _clean_content_block() returns None for thinking blocks whose signature
  is not a non-empty string
- Call sites in _convert_messages() skip dropped blocks; if an assistant
  message loses ALL its content this way, a minimal placeholder text block
  is emitted instead of an empty content array (which Anthropic rejects)
- _convert_to_chat_response() no longer persists response thinking blocks
  without a valid signature, so unsigned blocks can't re-enter history

Fixes microsoft/amplifier#330

Generated with Amplifier

Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cross-provider resume: thinking blocks with null signature passed to Anthropic API cause invalid_request_error

1 participant