google-gemini / google-gemini/gemini-cli
bug: abort-time history rollback becomes a silent no-op after mid-prompt CONTENT_TRUNCATED compression
- Dominant language
- TypeScript
- Stars
- 107k
- Forks
- 14.6k
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 45
Description
## What happened?
When a stream is aborted, `GeminiChat` rolls history back to a snapshot length captured once per prompt id (`promptOriginalHistoryLength`). If history shrank mid-prompt — which happens when auto-compression hits the `CONTENT_TRUNCATED` path and calls `setHistory(newHistory)` — the snapshot is now **larger** than the current history. `AgentHistory.rollback()` silently no-ops for out-of-range lengths, so the half-committed user/tool-response turn survives the abort, and `lastPromptTokenCount` is "restored" to a stale, larger value.
## Affected code
`packages/core/src/core/geminiChat.ts:736-748` (finally block):
```ts
if (isAborted && originalLength !== undefined) {
this.agentHistory.rollback(originalLength);
// ...
if (originalTokenCount !== undefined) {
this.lastPromptTokenCount = originalTokenCount;
}
```
`packages/core/src/utils/agentChatHistory.ts:53-57`:
```ts
rollback(length: number) {
if (length >= 0 && length <= this.history.length) {
this.history = this.history.slice(0, length);
} // out-of-range: silent no-op
}
```
`packages/core/src/core/client.ts:1240-1248` — the shrink that invalidates the snapshot:
```ts
} else if (info.compressionStatus === CompressionStatus.CONTENT_TRUNCATED) {
if (newHistory) {
this.getChat().setHistory(newHistory);
```
The snapshot is captured once per prompt at `geminiChat.ts:437-440` and reused across all sends of that prompt.
## How can this be reproduced?
1. Long session with a previously failed summarization so truncation is armed.
2. Trigger `CONTENT_TRUNCATED` compression mid-prompt (history shrinks below snapshot).
3. Cancel the next stream (ESC).
4. Inspect history: the aborted turn's trailing entries remain; token count was reset to the pre-truncation value.
## What did you expect to happen?
Rollback should clamp to the current reality (or re-capture the snapshot whenever history is replaced), leaving consistent state after an abort.
## Impact
Post-abort state corruption: dangling half-turns (e.g., a `functionResponse` needing the interrupted-placeholder patch) persist in history after cancellation.
## Suggested direction
Re-capture `promptOriginalHistoryLength` inside `setHistory()`/compression application, or make rollback `Math.min(length, this.history.length)` with the caller aware of the adjustment.
---
*Found by source audit on current `main` (commit `5411f113c`); platform-independent. No open issue/PR covering this was found (searched: abort rollback truncated history).*
Contributor guide
Research direction
Start with the abort cleanup in packages/core/src/core/geminiChat.ts:736-748, then trace the snapshot at lines 437-440 and history replacement in packages/core/src/core/client.ts:1240-1248. Read rollback in packages/core/src/utils/agentChatHistory.ts:53-57 and reproduce the CONTENT_TRUNCATED-then-abort sequence. Done means cancellation removes the interrupted turn and leaves history and lastPromptTokenCount consistent with the post-compression state.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- cli
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100