What happened?
When a conversation is rewound using the /rewind command or when an in-flight tool stream is aborted/interrupted, the in-memory chat history ends on a model turn (role: 'model').
If a subsequent API request is dispatched before a non-empty user message is recorded (e.g. following rewind, stream cancellation, or when trailing user turns are stripped/empty), GeminiChat.makeApiCallAndProcessStream sends the payload ending with a model turn directly to the Gemini API (ai.models.generateContentStream).
The Gemini API rejects the request with HTTP 400:
400 INVALID_ARGUMENT: Requests ending with a model turn are not supported.
At this point, the session fails with an unhandled API error and the user cannot proceed without discarding the session.
What did you expect to happen?
The client should enforce the Gemini API multi-turn role invariant prior to dispatching network requests, guaranteeing that request payloads never terminate on a model turn (by appending a continuation user turn such as Please continue., consistent with historyHardening.ts).
Client information
Client Information
- CLI Version: 0.63.0-nightly
- Operating System: macOS (Darwin arm64) / Linux / Windows
- Package:
@google/gemini-cli-core
Login information
Google Account / OAuth / API Key
Anything else we need to know?
Root cause analysis:
In packages/core/src/core/geminiChat.ts, makeApiCallAndProcessStream constructs contentsToUse from requestContents / contentsForPreviewModel. While historyHardening.ts contains role constraint enforcement (sentinel_end), makeApiCallAndProcessStream does not enforce the terminal user-role invariant before passing contentsToUse to stripToolCallIdPrefixes and generateContentStream.
What happened?
When a conversation is rewound using the
/rewindcommand or when an in-flight tool stream is aborted/interrupted, the in-memory chat history ends on a model turn (role: 'model').If a subsequent API request is dispatched before a non-empty user message is recorded (e.g. following rewind, stream cancellation, or when trailing user turns are stripped/empty),
GeminiChat.makeApiCallAndProcessStreamsends the payload ending with a model turn directly to the Gemini API (ai.models.generateContentStream).The Gemini API rejects the request with HTTP 400:
At this point, the session fails with an unhandled API error and the user cannot proceed without discarding the session.
What did you expect to happen?
The client should enforce the Gemini API multi-turn role invariant prior to dispatching network requests, guaranteeing that request payloads never terminate on a
modelturn (by appending a continuation user turn such asPlease continue., consistent withhistoryHardening.ts).Client information
Client Information
@google/gemini-cli-coreLogin information
Google Account / OAuth / API Key
Anything else we need to know?
Root cause analysis:
In
packages/core/src/core/geminiChat.ts,makeApiCallAndProcessStreamconstructscontentsToUsefromrequestContents/contentsForPreviewModel. WhilehistoryHardening.tscontains role constraint enforcement (sentinel_end),makeApiCallAndProcessStreamdoes not enforce the terminal user-role invariant before passingcontentsToUsetostripToolCallIdPrefixesandgenerateContentStream.