aaif-goose/goose · error
Missing thinking signature
Error message
Missing thinking signature
What it means
The second required field of a Snowflake Cortex thinking block: signature, the cryptographic proof string that lets the API accept the reasoning on the next turn. If a type "thinking" block has thinking text but its signature key is missing or not a string, response_to_message fails with 'Missing thinking signature'.
Source
Thrown at crates/goose-provider-types/src/formats/snowflake.rs:272
.to_string();
let input = content
.get("input")
.ok_or_else(|| anyhow!("Missing tool input"))?
.clone();
let tool_call = CallToolRequestParams::new(name).with_arguments(object(input));
message = message.with_tool_request(id, Ok(tool_call));
}
Some("thinking") => {
let thinking = content
.get("thinking")
.and_then(|t| t.as_str())
.ok_or_else(|| anyhow!("Missing thinking content"))?;
let signature = content
.get("signature")
.and_then(|s| s.as_str())
.ok_or_else(|| anyhow!("Missing thinking signature"))?;
message = message.with_thinking(thinking, signature);
}
Some("redacted_thinking") => {
let data = content
.get("data")
.and_then(|d| d.as_str())
.ok_or_else(|| anyhow!("Missing redacted_thinking data"))?;
message = message.with_redacted_thinking(data);
}
_ => {
// Ignore unrecognized content types
}
}
}
Ok(message)
}
View on GitHub (pinned to 3810898a74)
Solutions
- Confirm the raw block includes a string "signature" field
- If signatures are being scrubbed by a logging/privacy layer, exclude thinking blocks from scrubbing — the signature is required for multi-turn reasoning
- Fix fixtures to include a signature value (any non-empty string for tests)
- For endpoints without signature support, skip such blocks rather than failing the whole response
Example fix
// before
let signature = content
.get("signature")
.and_then(|s| s.as_str())
.ok_or_else(|| anyhow!("Missing thinking signature"))?;
// after - degrade to a text block instead of failing the conversion
let signature = match content.get("signature").and_then(|s| s.as_str()) {
Some(s) => s,
None => {
tracing::warn!("thinking block without signature; keeping text only");
message = message.with_text(thinking.to_string());
continue;
}
}; Defensive patterns
Strategy: validation
Validate before calling
for block in content_list {
if block.get("type").and_then(|t| t.as_str()) == Some("thinking")
&& block.get("signature").and_then(|s| s.as_str()).is_none()
{
anyhow::bail!("thinking block without string signature: {block}");
}
} Type guard
fn thinking_block_has_signature(block: &serde_json::Value) -> bool {
if block.get("type").and_then(|t| t.as_str()) != Some("thinking") {
return true;
}
block.get("signature").and_then(|v| v.as_str()).is_some()
} Try / catch
match response_to_message(&response) {
Ok(msg) => msg,
Err(err) if err.to_string().contains("Missing thinking signature") => {
tracing::warn!("thinking without signature cannot round-trip; dropping: {err}");
Message::assistant()
}
Err(err) => return Err(err),
} Prevention
- Exclude signature fields from redaction/scrubbing pipelines — multi-turn reasoning fails without them
- Avoid response-transforming proxies that trim long fields
- Test the full tool-use + reasoning round trip, not just single responses
When it happens
Trigger: A thinking block like {"type":"thinking","thinking":"..."} with no signature — typical of gateways that strip signatures for size, of fixtures that only model the text, or of schema variants where the signature lives one level up.
Common situations: Recording/replaying Cortex traffic with signatures redacted (they look like secrets and get scrubbed); prompt/response transformation layers trimming long fields; API schema drift moving signature elsewhere.
Related errors
- Missing thinking content
- Missing redacted_thinking data
- Missing tool_use id
- Missing tool_use name
- Missing tool input
AI-assisted analysis of aaif-goose/goose@3810898a74 (2026-08-16).
Data as JSON: /api/errors/e3ad772ac2ecb036.
Report an issue: GitHub.