gemini-cli - 💡(How to fix) Fix Malformed Gemini API contents can start with tool turns from router history slices or context sync [2 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
google-gemini/gemini-cli#26472Fetched 2026-05-05 06:03:23
View on GitHub
Comments
2
Participants
2
Timeline
4
Reactions
0
Timeline (top)
commented ×2labeled ×1renamed ×1

Root Cause

This leads to Gemini API 400 responses because the request starts with model and tool-call/function-response adjacency is no longer embedded in a valid conversation prefix.

Code Example

[
  { "role": "user", "parts": [{ "text": "<session_context>..." }] },
  { "role": "user", "parts": [{ "text": "Hi Gemini! ..." }] }
]

---

[
  { "role": "model", "parts": [{ "functionCall": { "name": "mcp_Coding_fetch_resource", "args": {} } }] },
  { "role": "user", "parts": [{ "functionResponse": { "name": "mcp_Coding_fetch_resource", "response": { "output": "..." } } }] },
  { "role": "user", "parts": [{ "text": "<session_context>..." }] },
  { "role": "user", "parts": [{ "text": "Hi Gemini! ..." }] }
]

---

model functionCall
user functionResponse
model functionCall
user functionResponse
...
user <session_context>
user original prompt
model answer
user next prompt

---

user <session_context>
   user "Hi Gemini! ..."

---

model functionCall mcp_Coding_fetch_resource
   user functionResponse mcp_Coding_fetch_resource

---

Existing graph:
[callA, responseA]

Authoritative full history after sync:
[session_context, prompt, callA, responseA]

Buggy graph update result:
[callA, responseA, session_context, prompt]

---

tool nodes were already in the context graph
+
full history sync appended earlier session turns later
+
legacy session_context was not filtered when misplaced
+
final API request did not defensively repair the malformed order
RAW_BUFFERClick to expand / collapse

What happened?

Gemini CLI can send a malformed Gemini API contents array after context/history management. A session that originally starts with:

[
  { "role": "user", "parts": [{ "text": "<session_context>..." }] },
  { "role": "user", "parts": [{ "text": "Hi Gemini! ..." }] }
]

can later be projected into an API request that starts with tool turns before the session header:

[
  { "role": "model", "parts": [{ "functionCall": { "name": "mcp_Coding_fetch_resource", "args": {} } }] },
  { "role": "user", "parts": [{ "functionResponse": { "name": "mcp_Coding_fetch_resource", "response": { "output": "..." } } }] },
  { "role": "user", "parts": [{ "text": "<session_context>..." }] },
  { "role": "user", "parts": [{ "text": "Hi Gemini! ..." }] }
]

This leads to Gemini API 400 responses because the request starts with model and tool-call/function-response adjacency is no longer embedded in a valid conversation prefix.

The bad ordering was observed after a session with MCP tool calls and large masked tool outputs. The same tool call/response pairs were duplicated ahead of the original session context.

What did you expect to happen?

The projected contents array should preserve the authoritative chronological history order:

  1. Session/environment context stays at the beginning, or is omitted from managed graph history and added only as a header.
  2. Tool call/function response pairs remain adjacent.
  3. The final request sent to Gemini never starts with a model turn.

If a history management path produces malformed order, the final API request builder should repair or reject it before calling Gemini.

Client information

Observed while debugging Gemini CLI source locally from google-gemini/gemini-cli on macOS. Exact /about output was not collected. The malformed request payload came from request logging/debug output.

Login information

Not login-specific. The failure happens before/during Gemini API validation of the request payload.

Detailed reasoning

The malformed request likely comes from a mismatch between "chat history as an ordered array" and "context history as a graph of observed nodes."

The Gemini API request is eventually sent as contents: Content[]. That array must preserve conversational order. In particular:

  • It should start with a user turn.
  • A model functionCall turn must be followed immediately by the matching user functionResponse.
  • The <session_context> turn is a prelude/header and should appear at the beginning or be managed separately, not in the middle.

The bad payload shows this ordering instead:

model functionCall
user functionResponse
model functionCall
user functionResponse
...
user <session_context>
user original prompt
model answer
user next prompt

That strongly suggests the original session turns were not transformed into tool turns. Instead, earlier or existing tool nodes were retained at the front, and the original session turns were later appended behind them.

The likely sequence is:

  1. The session begins normally:

    user <session_context>
    user "Hi Gemini! ..."
  2. The model performs MCP/resource tool calls:

    model functionCall mcp_Coding_fetch_resource
    user functionResponse mcp_Coding_fetch_resource
  3. Context management or history synchronization runs. This can happen through paths like setHistory, resume, compression, context projection, or tool-output masking.

  4. The context manager rebuilds a graph from the full chat history. The intended authoritative order is the array order from history.

  5. A full sync can be handled like an incremental update: existing graph nodes are kept in their previous order, and newly discovered nodes are appended. That is correct for a simple push, but wrong for a complete history replacement.

  6. If the graph had already observed tool-call nodes, those nodes stay at the front. If <session_context> or the initial prompt is seen later during a full sync, they are appended after the tool nodes.

A minimal abstract example:

Existing graph:
[callA, responseA]

Authoritative full history after sync:
[session_context, prompt, callA, responseA]

Buggy graph update result:
[callA, responseA, session_context, prompt]

That produces the model-first contents array and causes the 400.

There is a second contributing issue: the graph builder may only skip the legacy <session_context> turn when it appears at index 0. If a sync/replay path places it at index > 0, it can be treated as a normal user message and preserved in the graph. That makes the malformed middle-of-history session header possible.

The duplicated tool blocks are also explainable. Graph node identity is based on a combination of role, parts, function-call IDs, turn salt, and content hashes. If a tool call/response is re-observed after masking, truncation, synthetic IDs, or response content changes, the same semantic call can look like a new graph node. It then gets appended again instead of replacing/reordering the existing node.

So the failure is probably not "the first user message became a tool call." It is more likely:

tool nodes were already in the context graph
+
full history sync appended earlier session turns later
+
legacy session_context was not filtered when misplaced
+
final API request did not defensively repair the malformed order

Suspected source area

  • packages/core/src/context/contextManager.ts
  • packages/core/src/context/historyObserver.ts
  • packages/core/src/context/graph/toGraph.ts
  • packages/core/src/utils/historyHardening.ts
  • packages/core/src/core/geminiChat.ts

Suggested fix / regression coverage

  • On SYNC_FULL/CLEAR, rebuild the context buffer from the authoritative full node list in order.
  • Skip legacy <session_context> wherever it appears in graph building, not only at index 0.
  • Before sending to Gemini, harden the final contents array if it starts with model or contains misplaced <session_context>.
  • Add regression tests for full-sync reordering, misplaced session context, and model-first request repair.

extent analysis

TL;DR

Rebuild the context buffer from the authoritative full node list in order during SYNC_FULL/CLEAR operations to prevent malformed Gemini API requests.

Guidance

  • Review the contextManager.ts, historyObserver.ts, toGraph.ts, historyHardening.ts, and geminiChat.ts files for potential issues with context graph rebuilding and history synchronization.
  • Ensure that the final contents array is hardened before sending it to Gemini to prevent model-first requests and misplaced <session_context>.
  • Implement regression tests for full-sync reordering, misplaced session context, and model-first request repair to catch similar issues in the future.
  • Consider skipping legacy <session_context> wherever it appears in graph building, not only at index 0, to prevent it from being treated as a normal user message.

Example

No specific code example is provided due to the complexity of the issue and the need for a thorough review of the affected code areas.

Notes

The suggested fix focuses on rebuilding the context buffer and hardening the final contents array. However, a comprehensive solution may require additional changes to the context graph rebuilding and history synchronization logic.

Recommendation

Apply the suggested fix by rebuilding the context buffer from the authoritative full node list in order during SYNC_FULL/CLEAR operations and hardening the final contents array before sending it to Gemini. This approach addresses the root cause of the issue and helps prevent similar problems in the future.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

gemini-cli - 💡(How to fix) Fix Malformed Gemini API contents can start with tool turns from router history slices or context sync [2 comments, 2 participants]