Crates: cersei-types (missing stream event) + cersei-provider (parser drops it) Version: 0.1.9
AI disclosure: Claude Opus 4.8 high effort was used to write this issue, but I am indeed a real human who encountered this bug and will respond to comments on the issue.
Summary
When Anthropic extended thinking is enabled and the assistant uses tools, each thinking block comes back with a cryptographic signature that must be echoed back unchanged on the next turn. cersei never captures that signature off the stream, so the thinking block it sends back on the second turn has an empty signature, and the API rejects the conversation.
Error (second turn, first tool-using turn)
HTTP 400: {"type":"error","error":{"type":"invalid_request_error",
"message":"messages.1.content.0: Invalid `signature` in `thinking` block"}}
Root cause
The data model in cersei-types can hold a signature:
// crates/cersei-types/src/lib.rs
pub enum ContentBlock {
// ...
Thinking { thinking: String, signature: String },
// ...
}
…but the streaming layer has no way to deliver one. StreamEvent only has a thinking-text delta:
// crates/cersei-types/src/lib.rs
pub enum StreamEvent {
// ...
ThinkingDelta { index: usize, thinking: String },
// no SignatureDelta variant
}
Anthropic streams the signature as a separate content_block_delta of type: "signature_delta". The provider's SSE parser doesn't handle it — it falls through to _ => None and is discarded:
// crates/cersei-provider/src/anthropic.rs
"content_block_delta" => {
match delta_type {
"text_delta" => Some(StreamEvent::TextDelta { .. }),
"input_json_delta" => Some(StreamEvent::InputJsonDelta { .. }),
"thinking_delta" => Some(StreamEvent::ThinkingDelta { .. }),
_ => None, // "signature_delta" lands here and is dropped
}
}
So the accumulated ContentBlock::Thinking.signature stays empty, and when the agent loop re-sends history on the next turn the API rejects the unsigned thinking block.
Impact
Any multi-turn Anthropic conversation with extended thinking + tool use (i.e. the normal agentic loop in cersei-agent) fails on the second turn. Effectively, extended thinking can't be used with tools.
Suggested fix
- Add a
StreamEvent::SignatureDelta { index, signature } variant in cersei-types.
- Parse
"signature_delta" in cersei-provider's content_block_delta handler and emit it.
- Accumulate it onto the corresponding
ContentBlock::Thinking { signature } so it round-trips unchanged on subsequent requests.
(Anthropic also supports redacted_thinking blocks, which carry an opaque data field that must be preserved the same way.)
Environment
- cersei-types / cersei-provider / cersei-agent 0.1.9
- Anthropic API, model
claude-sonnet-4-6, thinking enabled with tool use
Crates:
cersei-types(missing stream event) +cersei-provider(parser drops it) Version: 0.1.9AI disclosure: Claude Opus 4.8 high effort was used to write this issue, but I am indeed a real human who encountered this bug and will respond to comments on the issue.
Summary
When Anthropic extended thinking is enabled and the assistant uses tools, each
thinkingblock comes back with a cryptographicsignaturethat must be echoed back unchanged on the next turn. cersei never captures that signature off the stream, so thethinkingblock it sends back on the second turn has an emptysignature, and the API rejects the conversation.Error (second turn, first tool-using turn)
Root cause
The data model in
cersei-typescan hold a signature:…but the streaming layer has no way to deliver one.
StreamEventonly has a thinking-text delta:Anthropic streams the signature as a separate
content_block_deltaoftype: "signature_delta". The provider's SSE parser doesn't handle it — it falls through to_ => Noneand is discarded:So the accumulated
ContentBlock::Thinking.signaturestays empty, and when the agent loop re-sends history on the next turn the API rejects the unsigned thinking block.Impact
Any multi-turn Anthropic conversation with extended thinking + tool use (i.e. the normal agentic loop in
cersei-agent) fails on the second turn. Effectively, extended thinking can't be used with tools.Suggested fix
StreamEvent::SignatureDelta { index, signature }variant incersei-types."signature_delta"incersei-provider'scontent_block_deltahandler and emit it.ContentBlock::Thinking { signature }so it round-trips unchanged on subsequent requests.(Anthropic also supports
redacted_thinkingblocks, which carry an opaquedatafield that must be preserved the same way.)Environment
claude-sonnet-4-6, thinking enabled with tool use