google-gemini / google-gemini/gemini-cli

bug: abort-time history rollback becomes a silent no-op after mid-prompt CONTENT_TRUNCATED compression

Open
#29,028 0 comments 0 reactions 0 assignees View on GitHub
area/agent status/need-triage
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.