google-gemini / google-gemini/gemini-cli

bug(cli): auto-nudge race in useGeminiStream creates duplicate streams and corrupts history on empty responses

Open
#29,071 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?

In `packages/cli/src/ui/hooks/useGeminiStream.ts` (main @ 812f7a2bc), empty model responses in a continuation trigger an automatic nudge via a floating promise that bypasses the responding guard:

`useGeminiStream.ts` lines 1638-1657 (inside `processGeminiStreamEvents`):
```ts
} else {
const hasVisibleText = geminiMessageBuffer.trim().length > 0;
if (isContinuation && !hasVisibleText && autoNudgeAttemptCountRef.current < 2) {
autoNudgeAttemptCountRef.current += 1;
// floating promise, no abort check
submitQueryRef.current?.([{ text: nudgeMessage }], { isContinuation: true }, prompt_id);
}
}
```

And lines 1698-1704 (inside `submitQuery`):
```ts
if ((isRespondingRef.current || streamingState === Responding || WaitingForConfirmation) && !options?.isContinuation)
return;
```

The nudge path uses `isContinuation: true`, so it **bypasses** the `isResponding` guard. If the model repeatedly returns empty (network glitch, transient API issue), two floating `submitQuery` calls race with the current `processGeminiStreamEvents` loop, sharing the same `prompt_id` and `abortSignal` that may already be aborted. This causes:

- Duplicate `sendMessageStream` calls with the same `prompt_id`
- Duplicated history entries (`[System: You successfully executed...]`)
- UI flicker between `Responding`/`Idle`
- The `autoNudgeAttemptCountRef` increments without proper synchronization

Additionally, cancellation path (lines 1996-2017) still calls `geminiClient.addHistory` for completed tools even when `turnCancelledRef` is true, permanently appending phantom tool responses that corrupt the next turn's role alternation (user→user → 400 Bad Request).

### What did you expect to happen?

- The auto-nudge should check `abortSignal.aborted` and `turnCancelledRef.current` before firing, and should await or properly sequence with the current stream processing
- The floating promise should be tracked and cancelled if the turn is cancelled
- `autoNudgeAttemptCountRef` should be reset appropriately and not increment racily
- Cancelled turns should not append tool responses to `geminiClient` history

Suggested fix:
```ts
if (isContinuation && !hasVisibleText && !signal.aborted && !turnCancelledRef.current && autoNudgeAttemptCountRef.current < 2) {
autoNudgeAttemptCountRef.current += 1;
await submitQueryRef.current?.(...);
}
```
And in cancellation path, skip `addHistory` when `turnCancelledRef` is true.

### Client information

- Source-level finding verified against upstream `main` at commit `812f7a2bc`
- File: `packages/cli/src/ui/hooks/useGeminiStream.ts:1638-1657, 1698-1704, 1996-2017`
- Affects all platforms

### Login information

Not applicable.

### Anything else we need to know?

Sources:
- https://github.com/google-gemini/gemini-cli/blob/812f7a2bc/packages/cli/src/ui/hooks/useGeminiStream.ts#L1638-L1657
- https://github.com/google-gemini/gemini-cli/blob/812f7a2bc/packages/cli/src/ui/hooks/useGeminiStream.ts#L1698-L1704
- https://github.com/google-gemini/gemini-cli/blob/812f7a2bc/packages/cli/src/ui/hooks/useGeminiStream.ts#L1996-L2017
- Related issue #28909 discusses nudge message placement in system prompt vs our finding about race/floating promise - distinct root cause

Searched existing issues for "auto-nudge", "useGeminiStream race", "floating promise" — no open duplicate found (related #28909 is about placement, not race).

Contributor guide

Open the contributing guide

Research direction

Read packages/cli/src/ui/hooks/useGeminiStream.ts at the cited processGeminiStreamEvents, submitQuery, and cancellation sections, tracing the auto-nudge promise, abort signal, responding guard, and turnCancelledRef. Verify that empty continuations cannot create duplicate streams or corrupt history, that cancellation prevents tool responses from being appended, and that autoNudgeAttemptCountRef remains synchronized.

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
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.