google-gemini / google-gemini/gemini-cli

ui.errorVerbosity = "full", does not display retry progress indicators in the UI

Open
#28,340 7 comments 0 reactions 0 assignees View on GitHub
area/core effort/small kind/bug priority/p1 status/bot-triaged
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

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.