What happened?
When using gemini-cli with the gemini-3.1-pro-preview model, the CLI crashes with a 400 Bad Request API Error.
The error log shows:
"Function call is missing a thought_signature in functionCall parts." (or in executableCode parts)
This consistently happens when the model emits multiple tools in the same response (e.g., an update_topic tool call followed by a run_shell_command). The first tool call succeeds, but the subsequent ones cause the API request to fail.
What did you expect to happen?
The CLI should successfully process all function calls without crashing, correctly attaching the synthetic thought_signature to all parallel tool calls and executable code blocks in the turn.
Client information
CLI Version: 0.41.1
OS: linux
Auth Method: gemini-api-key
Anything else we need to know?
We debugged the minified bundle (specifically chunk-NET4RIEQ.js) and found the root cause in the ensureActiveLoopHasThoughtSignatures function. There are three bugs in the loop that injects the synthetic signatures:
- Incorrect Nesting: The synthetic signature is injected at the root of the
part object, but the API requires it to be nested inside the part.functionCall object.
- Premature Break: The loop contains a
break; statement that stops iterating through the parts array as soon as it finds the first functionCall.
- Missing Tool Types: The function completely ignores
executableCode blocks, which also require this signature in Gemini 3.1.
Note: Aggressive object cloning via JSON.parse(JSON.stringify()) must be avoided, as it strips prototypes and breaks the SDK's turn tracking, causing "Please ensure that function call turn comes immediately after a user turn" errors. Shallow copies must be used.
Proposed Fix:
Replace ensureActiveLoopHasThoughtSignatures and stripThoughtsFromHistory with the following implementation using shallow copies:
ensureActiveLoopHasThoughtSignatures(requestContents) {
const newContents = requestContents.slice();
for (let i = 0; i < newContents.length; i++) {
const content = newContents[i];
if (content.role === "model" && content.parts) {
const newParts = content.parts.slice();
let changed = false;
for (let j = 0; j < newParts.length; j++) {
const part = newParts[j];
if (part.functionCall) {
newParts[j] = {
...part,
thoughtSignature: SYNTHETIC_THOUGHT_SIGNATURE,
thought_signature: SYNTHETIC_THOUGHT_SIGNATURE,
functionCall: {
...part.functionCall,
thoughtSignature: SYNTHETIC_THOUGHT_SIGNATURE,
thought_signature: SYNTHETIC_THOUGHT_SIGNATURE
}
};
changed = true;
}
if (part.executableCode) {
newParts[j] = {
...newParts[j],
thoughtSignature: SYNTHETIC_THOUGHT_SIGNATURE,
thought_signature: SYNTHETIC_THOUGHT_SIGNATURE,
executableCode: {
...part.executableCode,
thoughtSignature: SYNTHETIC_THOUGHT_SIGNATURE,
thought_signature: SYNTHETIC_THOUGHT_SIGNATURE
}
};
changed = true;
}
}
if (changed) {
newContents[i] = { ...content, parts: newParts };
}
}
}
return newContents;
}
stripThoughtsFromHistory() {
this.agentHistory.map((content) => {
const newContent = { ...content };
if (newContent.parts) {
newContent.parts = newContent.parts.map((part) => {
if (part && typeof part === "object") {
const newPart = { ...part };
delete newPart.thoughtSignature;
delete newPart.thought_signature;
if (newPart.functionCall) {
newPart.functionCall = { ...newPart.functionCall };
delete newPart.functionCall.thoughtSignature;
delete newPart.functionCall.thought_signature;
}
if (newPart.executableCode) {
newPart.executableCode = { ...newPart.executableCode };
delete newPart.executableCode.thoughtSignature;
delete newPart.executableCode.thought_signature;
}
return newPart;
}
return part;
});
}
return newContent;
});
}
(Note: This update was automatically analyzed and submitted by Opencode using the Gemini 3.1 Pro Preview model, which successfully diagnosed its own crash loop and hot-patched the minified bundle to restore functionality.)
What happened?
When using
gemini-cliwith thegemini-3.1-pro-previewmodel, the CLI crashes with a 400 Bad Request API Error.The error log shows:
"Function call is missing a thought_signature in functionCall parts."(or inexecutableCodeparts)This consistently happens when the model emits multiple tools in the same response (e.g., an
update_topictool call followed by arun_shell_command). The first tool call succeeds, but the subsequent ones cause the API request to fail.What did you expect to happen?
The CLI should successfully process all function calls without crashing, correctly attaching the synthetic
thought_signatureto all parallel tool calls and executable code blocks in the turn.Client information
CLI Version: 0.41.1
OS: linux
Auth Method: gemini-api-key
Anything else we need to know?
We debugged the minified bundle (specifically
chunk-NET4RIEQ.js) and found the root cause in theensureActiveLoopHasThoughtSignaturesfunction. There are three bugs in the loop that injects the synthetic signatures:partobject, but the API requires it to be nested inside thepart.functionCallobject.break;statement that stops iterating through the parts array as soon as it finds the firstfunctionCall.executableCodeblocks, which also require this signature in Gemini 3.1.Note: Aggressive object cloning via
JSON.parse(JSON.stringify())must be avoided, as it strips prototypes and breaks the SDK's turn tracking, causing "Please ensure that function call turn comes immediately after a user turn" errors. Shallow copies must be used.Proposed Fix:
Replace
ensureActiveLoopHasThoughtSignaturesandstripThoughtsFromHistorywith the following implementation using shallow copies:(Note: This update was automatically analyzed and submitted by Opencode using the Gemini 3.1 Pro Preview model, which successfully diagnosed its own crash loop and hot-patched the minified bundle to restore functionality.)