google-gemini / google-gemini/gemini-cli
ui.errorVerbosity = "full", does not display retry progress indicators in the UI
- Dominant language
- TypeScript
- Stars
- 107k
- Forks
- 14.6k
- Avg merge
- 2d 3h
- Merged PRs (30d)
- 45
Description
### What happened?
During a connection failure and recovery retry sequence (with `errorVerbosity:
"full"`), the terminal loading spinner fails to display the retry progress
indicators (`"Trying to reach [...] (Attempt (N/10)"`)
- The spinner renders `"Thinking..."`
- Background recovery transitions execute, but the UI state stays on the
"Thinking" spinner.
### What did you expect to happen?
During a connection failure and recovery retry sequence (with `errorVerbosity:
"full"`), the terminal loading spinner should display the retry progress
indicators (`"Trying to reach [...] (Attempt (N/10)"`) introduced in
`feat(core/ui): enhance retry mechanism and UX (#16489)` (`4afd3741d`)
### Client information
Client Information
Run `gemini` to enter the interactive CLI, then run the `/about` command.
```console
> /about
│ About Gemini CLI │
│ │
│ CLI Version 0.49.0 │
│ Git Commit 5d402f89f │
│ Model Auto │
│ Sandbox no sandbox │
│ OS linux │
│ Auth Method Signed in with Google () │
│ Tier Gemini Code Assist Standard │
│ GCP Project │
│ IDE Client IDE │
```
### Login information
Google Account
### Anything else we need to know?
## Technical Bug Details
This bug is caused by three rendering layer issues:
### Root Cause 1: Missing Prop Propagation in StatusRow
The `useLoadingIndicator()` hook computes the correct `currentLoadingPhrase` containing the retry status. In `AppContainer.tsx`, this is passed to ``.
However, the prop is discarded:
1. `StatusRowProps` interface omits `currentLoadingPhrase`.
2. `` invokes `` without passing `currentLoadingPhrase`.
3. Inside ``, `currentLoadingPhrase` is hardcoded to `undefined` on render unless an active background hook overrides it.
### Root Cause 2: Priority Conflation in LoadingIndicator
`currentLoadingPhrase` is used for both low-priority cosmetic phrases (tips) and high-priority system statuses (retries).
Because they share a single variable, `` enforces a strict override where active model thoughts (`thought?.subject`) take precedence over `currentLoadingPhrase`. During an active request cycle, `'Thinking...'` unconditionally suppresses the retry status.
### Root Cause 3: Missing Prop in Composer
`` is rendered inside `` (`packages/cli/src/ui/components/Composer.tsx`). `` does not read or pass `currentLoadingPhrase` down to ``. During execution, the prop is perpetually `undefined`.
---
## Reproduction Case
The following test case (for `StatusRow.test.tsx`) asserts the expected behavior. On the unmodified codebase, **this test fails** because the retry prop is discarded and overridden by the thought subject `'Thinking...'`.
```typescript
it('fails to render currentLoadingPhrase when thought subject is present', async () => {
(useComposerStatus as Mock).mockReturnValue({
isInteractiveShellWaiting: false,
showLoadingIndicator: true,
showTips: true,
showWit: true,
modeContentObj: null,
showMinimalContext: false,
});
const uiState: Partial = {
...defaultUiState,
thought: { subject: 'Thinking...' } as unknown as ThoughtSummary,
};
const { lastFrame, waitUntilReady } = await renderWithProviders(
,
{
width: 100,
uiState,
},
);
await waitUntilReady();
const output = lastFrame();
// Assertion fails: output contains 'Thinking...' instead of the retry phrase.
expect(output).toContain('Trying to reach gemini-2.5-flash (Attempt 1/5)');
expect(output).not.toContain('Thinking...');
});
```
---
## Possible Fix
> **Disclaimer:** This section contains a proposed architectural solution designed and implemented by Gemini to resolve the bug. It is provided for reference and is not necessarily what the final upstream canonical fix must be.
The status indicator architecture can be refactored to decouple cosmetic loading text from critical connection status using a dedicated variable.
### 1. Separate Variables (`useLoadingIndicator.ts`)
Return statuses as distinct variables:
* `currentLoadingPhrase`: Cosmetic text (tips, witty remarks).
* `statusPhrase`: Critical connection retry/fallback status.
### 2. Context Fallback (`StatusRow.tsx`)
Pass `statusPhrase` directly through `uiState` (`UIStateContext.tsx`). `` reads `statusPhrase` from the `useUIState()` hook, bypassing the `` prop chain disconnect.
### 3. Priority Rendering (`LoadingIndicator.tsx`)
`` evaluates `statusPhrase` directly, prioritizing it over `thought?.subject` without string-prefix matching:
```typescript
const primaryText =
currentLoadingPhrase === INTERACTIVE_SHELL_WAITING_PHRASE || statusPhrase
? (statusPhrase ?? currentLoadingPhrase)
: thought?.subject
? (thoughtLabel ?? thought.subject)
: currentLoadingPhrase || ...
```
Contributor guide
Research direction
Start with the failing StatusRow.test.tsx case, then trace currentLoadingPhrase through packages/cli/src/ui/components/Composer.tsx, AppContainer.tsx, StatusRow, StatusNode, useLoadingIndicator.ts, and LoadingIndicator.tsx. Check where the retry phrase is discarded or overridden by the thought subject. Done means the retry indicator renders during recovery and Thinking... does not replace it; run the StatusRow tests to verify the behavior.
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
- Clearly specified
- Newbie friendliness
- 67/100