Skip to content

fix: extend empty-text gate to Bedrock and Vertex signature namespaces - #1

Closed
omer-koren wants to merge 13 commits into
edevil:fix/preserve-thinking-block-signaturesfrom
omer-koren:contrib/bedrock-vertex-signature-gate
Closed

fix: extend empty-text gate to Bedrock and Vertex signature namespaces#1
omer-koren wants to merge 13 commits into
edevil:fix/preserve-thinking-block-signaturesfrom
omer-koren:contrib/bedrock-vertex-signature-gate

Conversation

@omer-koren

Copy link
Copy Markdown

Small follow-up to your PR that extends the hasSignedReasoning gate to cover AWS Bedrock and GCP Vertex AI. Feel free to squash into your PR, take the ideas, or close — whichever is easiest for you.

The bug

The gate currently only matches direct-Anthropic signatures:

consthasSignedReasoning=msg.parts.some((p)=>p.type==="reasoning"&&(p.metadataasany)?.anthropic?.signature!=null,)

When Claude is hosted on AWS Bedrock (amazon-bedrock/anthropic.*) or GCP Vertex AI, the reasoning part's metadata stores the signature under a provider-scoped namespace:

  • metadata.bedrock.signature — AWS Bedrock
  • metadata.vertex.signature — GCP Vertex AI

The AI SDK's Anthropic adapter keeps the namespace on the host that answered the call, so the gate never fires for Bedrock/Vertex users. The empty-text substitution is skipped, and on compaction/replay the message still trips Anthropic's:

messages.N.content.M: 'thinking' or 'redacted_thinking' blocks in the latest assistant message cannot be modified.

Repro

On Bedrock Claude Opus 4.7 (amazon-bedrock/anthropic.claude-opus-4-7), inspecting a reasoning part from the SQLite session DB:

{
"type": "reasoning",
"text": "...",
"metadata": { "bedrock": { "signature": "Es4EClkIDRABGAIq..." } }
}

With only the anthropic namespace checked, hasSignedReasoning returns false → empty text parts between signed reasoning blocks remain "" → Anthropic rejects on the next turn or manual compact.

The fix

Extend the check to any of the three provider namespaces:

consthasSignedReasoning=msg.parts.some((p)=>p.type==="reasoning"&&((p.metadataasany)?.anthropic?.signature!=null||(p.metadataasany)?.bedrock?.signature!=null||(p.metadataasany)?.vertex?.signature!=null),)

The "non-Anthropic providers' reasoning doesn't position-validate" rationale from your existing comment still holds — OpenAI Responses, Gemini thinking, Copilot reasoningOpaque don't write under these three keys, so they continue to have empty text filtered normally.

Tests

Two new tests alongside your existing one, covering the Bedrock and Vertex paths. Same structure as leaves empty text alone when reasoning has no Anthropic signature — just flipping the metadata namespace.

  • 29 tests pass locally (bun test test/session/message-v2.test.ts)
  • LSP diagnostics clean on the changed file
  • Production-verified against a real Bedrock Opus 4.7 session that was reliably reproducing the cannot be modified error on manual compact — the error no longer triggers after this change

Verification

The aws-sdk/client-bedrock-runtime adapter and google-cloud/vertexai both preserve the provider namespace on streamed response metadata rather than normalizing to anthropic — so the extension is necessary, not redundant.


Thanks for the original fix — saved us from a persistent production issue. Happy to adjust/squash/close as you prefer.

rekram1-nodeand others added 13 commits April 22, 2026 00:35
…ng blocks
Anthropic adaptive thinking (Opus 4.6+) emits empty text parts between
thinking blocks. Dropping them invalidates thinking block signatures
('thinking blocks cannot be modified'), but sending them as '' fails
validation ('text content blocks must be non-empty'). Substitute a
single space so the part survives all filters and Anthropic accepts it.
Gated on reasoning having an Anthropic signature (metadata.anthropic.
signature != null) so other providers' reasoning — which don't
position-validate — continue to have empty text filtered normally.
Closesanomalyco#16748
The hasSignedReasoning gate introduced in this PR only matches Anthropic's
direct-API signature path (metadata.anthropic.signature). When Claude is
hosted on AWS Bedrock or GCP Vertex AI, the reasoning part metadata stores
the signature under metadata.bedrock.signature / metadata.vertex.signature
respectively. The gate never fires for those providers, so empty text parts
between signed reasoning blocks are not substituted with a space, and
Anthropic/Bedrock/Vertex still reject the compacted message with:
messages.N.content.M: 'thinking' or 'redacted_thinking' blocks in the
latest assistant message cannot be modified
Extend the check to match signatures under any of the three provider
namespaces. Adds two tests covering the Bedrock and Vertex paths.
Sign up for freeto 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.

6 participants

@omer-koren@rekram1-node@Hona@Brendonovich@nexxeln@edevil