google-gemini / google-gemini/gemini-cli
bug(cli): impure state updater in useInputHistoryStore schedules nested setState inside another setState updater
- 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
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