Hmbown/CodeWhale · error
Gemini model `{model}` requires a thought signature to repla
Error message
Gemini model `{model}` requires a thought signature to replay tool call `{id}`, but none was captured (the turn predates signature capture, or the provider omitted it). Start a new session before using tools on this route. What it means
Before replaying a session to a Gemini route that requires thought signatures (Gemini 2.x thinking models with function calling), the client walks every historical message's `tool_calls` and requires each call to carry `extra_content.google.thought_signature`. A missing signature aborts the request, because the provider would reject the replayed turn; the message tells you to start a new session.
Source
Thrown at crates/tui/src/client/chat.rs:666
messages: &[Value],
) -> Result<()> {
if !is_exact_google_chat_route(provider, base_url)
|| !google_model_requires_thought_signatures(model)
{
return Ok(());
}
for message in messages {
let Some(tool_calls) = message.get("tool_calls").and_then(Value::as_array) else {
continue;
};
for call in tool_calls {
let missing = call
.pointer("/extra_content/google/thought_signature")
.and_then(Value::as_str)
.is_none();
if missing {
let id = call.get("id").and_then(Value::as_str).unwrap_or("?");
anyhow::bail!(
"Gemini model `{model}` requires a thought signature to replay tool call \
`{id}`, but none was captured (the turn predates signature capture, or \
the provider omitted it). Start a new session before using tools on \
this route."
);
}
}
}
Ok(())
}
/// Captured Google signatures ride on tool calls as
/// `extra_content.google.thought_signature`. Only the exact Google route
/// may carry them on the wire; every other provider gets them stripped so
/// a route switch never leaks Google-only fields to a foreign gateway.
fn strip_google_tool_call_extra_content(messages: &mut [Value]) {
for message in messages {
let Some(tool_calls) = message.get_mut("tool_calls").and_then(Value::as_array_mut) else {View on GitHub (pinned to 8880682c63)
Solutions
- Start a new session before using tools on this Gemini route, exactly as the message instructs
- Re-issue the tool call in a fresh turn so a new signature is captured and stored
- Avoid switching models mid-session when the session contains tool calls
- Stay on a route/model that does not require thought signatures for existing sessions
Defensive patterns
Strategy: validation
Validate before calling
fn tool_calls_have_thought_signatures(messages: &[serde_json::Value]) -> bool {
messages.iter().all(|m| {
m.get("tool_calls")
.and_then(|t| t.as_array())
.map(|calls| {
calls.iter().all(|c| {
c.pointer("/extra_content/google/thought_signature")
.and_then(|s| s.as_str())
.is_some()
})
})
.unwrap_or(true)
})
}
// Run before switching an existing session to a signature-requiring Gemini route:
if route_requires_thought_signatures(model) && !tool_calls_have_thought_signatures(&history) {
start_new_session();
} Type guard
fn tool_calls_have_thought_signatures(messages: &[serde_json::Value]) -> bool {
messages.iter().all(|m| {
m.get("tool_calls")
.and_then(|t| t.as_array())
.map(|calls| {
calls.iter().all(|c| {
c.pointer("/extra_content/google/thought_signature")
.and_then(|s| s.as_str())
.is_some()
})
})
.unwrap_or(true)
})
} Try / catch
match client.send(request).await {
Err(e) if e.to_string().contains("requires a thought signature") => {
// History cannot be replayed on this route; do not retry the same history
start_new_session_and_replay_user_intent().await;
}
result => result,
} Prevention
- Start a fresh session when moving a tool-using conversation onto Gemini thinking routes
- Do not switch models mid-session when the history contains tool calls
- Verify signature presence in stored tool calls before route changes, not after the provider rejects
When it happens
Trigger: Resuming a session whose tool-call turns predate signature capture; switching an existing tool-using session to a signature-requiring Gemini model mid-conversation; the provider omitted the signature in an earlier response so it was never stored.
Common situations: Upgrading the app and reopening old sessions; changing the model/route on a long tool-using session; provider-side inconsistency in attaching signatures.
Related errors
- This browser session is not authenticated. Restart `codewhal
- failed to enter ACP session cwd {}: {err}
- MCP session expired: {error}
- MCP Streamable HTTP session expired; retry with a new sessio
- MCP session expired (transport=sse endpoint={} status={}): {
AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16).
Data as JSON: /api/errors/4f8fcfdb1dae79f8.
Report an issue: GitHub.