{"record":{"id":"4f8fcfdb1dae79f8","repo":"Hmbown/CodeWhale","slug":"gemini-model-model-requires-a-thought-signatur","errorCode":null,"errorMessage":"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.","messagePattern":"Gemini model `(.+?)` requires a thought signature to replay tool call `(.+?)`, but none was captured \\(the turn predates signature capture, or the provider omitted it\\)\\. Start a new session before using tools on this route\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/client/chat.rs","lineNumber":666,"sourceCode":"    messages: &[Value],\n) -> Result<()> {\n    if !is_exact_google_chat_route(provider, base_url)\n        || !google_model_requires_thought_signatures(model)\n    {\n        return Ok(());\n    }\n    for message in messages {\n        let Some(tool_calls) = message.get(\"tool_calls\").and_then(Value::as_array) else {\n            continue;\n        };\n        for call in tool_calls {\n            let missing = call\n                .pointer(\"/extra_content/google/thought_signature\")\n                .and_then(Value::as_str)\n                .is_none();\n            if missing {\n                let id = call.get(\"id\").and_then(Value::as_str).unwrap_or(\"?\");\n                anyhow::bail!(\n                    \"Gemini model `{model}` requires a thought signature to replay tool call \\\n                     `{id}`, but none was captured (the turn predates signature capture, or \\\n                     the provider omitted it). Start a new session before using tools on \\\n                     this route.\"\n                );\n            }\n        }\n    }\n    Ok(())\n}\n\n/// Captured Google signatures ride on tool calls as\n/// `extra_content.google.thought_signature`. Only the exact Google route\n/// may carry them on the wire; every other provider gets them stripped so\n/// a route switch never leaks Google-only fields to a foreign gateway.\nfn strip_google_tool_call_extra_content(messages: &mut [Value]) {\n    for message in messages {\n        let Some(tool_calls) = message.get_mut(\"tool_calls\").and_then(Value::as_array_mut) else {","sourceCodeStart":648,"sourceCodeEnd":684,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/crates/tui/src/client/chat.rs#L648-L684","documentation":"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.","triggerScenarios":"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.","commonSituations":"Upgrading the app and reopening old sessions; changing the model/route on a long tool-using session; provider-side inconsistency in attaching signatures.","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"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"fn tool_calls_have_thought_signatures(messages: &[serde_json::Value]) -> bool {\n    messages.iter().all(|m| {\n        m.get(\"tool_calls\")\n            .and_then(|t| t.as_array())\n            .map(|calls| {\n                calls.iter().all(|c| {\n                    c.pointer(\"/extra_content/google/thought_signature\")\n                        .and_then(|s| s.as_str())\n                        .is_some()\n                })\n            })\n            .unwrap_or(true)\n    })\n}\n\n// Run before switching an existing session to a signature-requiring Gemini route:\nif route_requires_thought_signatures(model) && !tool_calls_have_thought_signatures(&history) {\n    start_new_session();\n}","typeGuard":"fn tool_calls_have_thought_signatures(messages: &[serde_json::Value]) -> bool {\n    messages.iter().all(|m| {\n        m.get(\"tool_calls\")\n            .and_then(|t| t.as_array())\n            .map(|calls| {\n                calls.iter().all(|c| {\n                    c.pointer(\"/extra_content/google/thought_signature\")\n                        .and_then(|s| s.as_str())\n                        .is_some()\n                })\n            })\n            .unwrap_or(true)\n    })\n}","tryCatchPattern":"match client.send(request).await {\n    Err(e) if e.to_string().contains(\"requires a thought signature\") => {\n        // History cannot be replayed on this route; do not retry the same history\n        start_new_session_and_replay_user_intent().await;\n    }\n    result => result,\n}","preventionTips":["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"],"tags":["gemini","tool-calls","session","replay"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}