google-gemini / google-gemini/gemini-cli

bug(cli): impure state updater in useInputHistoryStore schedules nested setState inside another setState updater

Open
#29,046 2 comments 0 reactions 0 assignees View on GitHub
area/agent effort/small kind/bug priority/p2 status/manual-triage status/need-information
Dominant language
TypeScript
Stars
107k
Forks
14.6k
Avg merge
2d 3h
Merged PRs (30d)
45

Description

## What happened?

`useInputHistoryStore.addInput()` calls `setPastSessionMessages(...)` **inside** the updater function of `setCurrentSessionMessages(...)`, and runs the side-effecting `recalculateHistory()` (which itself calls another setState) inside that nested updater. React state updaters must be pure; under StrictMode double-invocation the nested work runs twice per submit, and under batching the inner update fires once per queued outer update. Currently idempotent, so corruption is latent rather than reproducible — but this is exactly the pattern React documents as unsupported and that breaks under concurrent features.

## Affected code

`packages/cli/src/ui/hooks/useInputHistoryStore.ts:93-107`:

```ts
setCurrentSessionMessages((prevCurrent) => {
const newCurrentSession = [...prevCurrent, trimmedInput];

setPastSessionMessages((prevPast) => {
recalculateHistory(
newCurrentSession.slice().reverse(), // Convert to newest first
prevPast,
);
return prevPast; // No change to past messages
});

return newCurrentSession;
});
```

## Why this is wrong

- Updater functions may be invoked multiple times (double-invoke in StrictMode/dev, re-invocations under batching).
- Side effects (another component-state update) inside an updater violate the contract and make behavior timing-dependent.
- The inner updater returns `prevPast` unchanged purely to piggyback the side effect.

## How can this be reproduced?

Run the CLI test harness in StrictMode and submit several inputs rapidly: the history recalculation executes twice per input (observable via instrumentation on `recalculateHistory`).

## What did you expect to happen?

A single, pure state transition per submit.

## Suggested direction

Compute both slices outside any updater (or use one reducer holding `{current, past}`), then set them in sequence; call `recalculateHistory` in an effect keyed to the committed values.

---

*Found by source audit on current `main` (commit `5411f113c`); platform-independent. No open issue/PR covering this was found (searched: input history store setState updater).*

Contributor guide

Open the contributing guide

Research direction

Read packages/cli/src/ui/hooks/useInputHistoryStore.ts:93-107 and trace addInput, setCurrentSessionMessages, setPastSessionMessages, and recalculateHistory. Run the CLI test harness in StrictMode with rapid submissions to observe the duplicate recalculation. Done means each submit performs a single pure state transition without nested updater work.

Written by the indexing model from the issue text.

Assessment

Tech stack
react, typescript
Domain
cli
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.