Skip to content

fix: Add isRespondingRef for synchronous blocking and await the retry #20261

Closed
ishaanxgupta wants to merge 10 commits into
google-gemini:mainfrom
ishaanxgupta:ishaan/disable
Closed

fix: Add isRespondingRef for synchronous blocking and await the retry #20261
ishaanxgupta wants to merge 10 commits into
google-gemini:mainfrom
ishaanxgupta:ishaan/disable

Conversation

@ishaanxgupta

@ishaanxgupta ishaanxgupta commented Feb 25, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Added synchronous lock ref + synced setter: [isRespondingRef + setRespondingState]
  • Updated submitQuery guard to use immediate ref check (instead of relying only on React state): [submitQuery guard]
  • Set the ref immediately when a submit starts, so same-tick prompts are blocked: [isRespondingRef.current = true
  • Changed loop-detection "disable" retry to [await submitQuery(...)] [onComplete retry await]
  • Kept state/ref cleanup centralized via setRespondingState(false) in finally and other termination paths.

Related Issues

fixes: #17071

Pre-Merge Checklist

  • Updated relevant documentation and README (if needed)
  • Added/updated tests (if needed)
  • Noted breaking changes (if any)
  • Validated on required platforms/methods:
    • MacOS
      • npm run
      • npx
      • Docker
      • Podman
      • Seatbelt
    • Windows
      • npm run
      • npx
      • Docker
    • Linux
      • npm run
      • npx
      • Docker

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @ishaanxgupta, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request refines the state management for query submission and response handling, primarily by introducing a synchronous reference (isRespondingRef) alongside the existing React state. This change aims to prevent race conditions where rapid user input or system events might bypass state checks due to the asynchronous nature of useState. By centralizing state updates and ensuring immediate blocking, the system's responsiveness and reliability during active operations are significantly improved, particularly in scenarios like loop detection retries.

Highlights

  • Synchronous State Management: Introduced isRespondingRef (a useRef hook) to provide an immediate, synchronous way to check if the system is currently responding, addressing potential race conditions with React's asynchronous useState updates.
  • Centralized State Setter: Created setRespondingState as a useCallback hook that simultaneously updates both the isRespondingRef.current value and the isResponding React state, ensuring consistency across synchronous and asynchronous contexts.
  • Immediate Query Blocking: Modified the submitQuery guard to check isRespondingRef.current directly, allowing for immediate blocking of new queries (e.g., same-tick prompts) without waiting for React state updates. The isRespondingRef is now set to true at the very beginning of the submitQuery process.
  • Awaited Loop Detection Retry: Updated the loop detection retry mechanism to await the submitQuery call, ensuring that the system properly waits for the retry to complete before proceeding, preventing further issues during recovery.
  • Consistent State Cleanup: Refactored all instances of setIsResponding(false) to use the new setRespondingState(false) function, centralizing the cleanup logic and ensuring both the ref and React state are reset consistently upon query termination or cancellation.
Changelog
  • packages/cli/src/ui/hooks/useGeminiStream.ts
    • Introduced isRespondingRef to track response status synchronously.
    • Added setRespondingState callback to update both isRespondingRef and isResponding state.
    • Replaced direct setIsResponding calls with setRespondingState across various lifecycle methods and event handlers.
    • Updated the submitQuery guard to use isRespondingRef.current for immediate blocking of new queries.
    • Ensured isRespondingRef.current is set to true immediately upon query submission.
    • Modified the loop detection retry logic to await the submitQuery call.
    • Updated dependency arrays for useCallback and useEffect hooks to include setRespondingState.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a synchronous lock using useRef to prevent race conditions when submitting queries, which is a solid approach. The implementation is mostly correct, with the new setRespondingState helper being consistently applied. However, I've identified a critical issue where the new lock is not released in all code paths, which can lead to a deadlock and make the UI unresponsive. My review includes a comment detailing the scenarios and providing recommendations to fix it.

Comment thread packages/cli/src/ui/hooks/useGeminiStream.ts
@gemini-cli gemini-cli Bot added priority/p1 Important and should be addressed in the near term. area/core Issues related to User Interface, OS Support, Core Functionality help wanted We will accept PRs from all issues marked as "help wanted". Thanks for your support! labels Feb 25, 2026
@ishaanxgupta ishaanxgupta marked this pull request as ready for review February 26, 2026 17:48
@ishaanxgupta ishaanxgupta requested a review from a team as a code owner February 26, 2026 17:48
Comment thread packages/cli/src/ui/hooks/useGeminiStream.ts Outdated
@ishaanxgupta

Copy link
Copy Markdown
Contributor Author

@scidomino please have a look

@scidomino

Copy link
Copy Markdown
Collaborator

@ishaanxgupta Sorry, due to overwhelming demand, I am no longer reviewing external PRs unless:

  • You are assigned to the issue
  • The issue is marked "help-wanted"

@nidhishgajjar

Copy link
Copy Markdown

Orb Code Review (powered by GLM-4.7 on Orb Cloud)

Summary

This PR addresses a race condition in synchronous query submission by introducing isRespondingRef as a separate ref (independent of React state) to block duplicate submissions within the same event loop tick. However, the PR also bundles massive unrelated cleanup: 1,951 files changed with over 1 million deletions, removing entire subsystems (ACP, gemma, performance tests) and simplifying CLI startup.

Architecture

Core Fix (packages/cli/src/ui/hooks/useGeminiStream.ts):

The Problem:
Previously, the submitQuery guard relied on isRespondingRef.current (from useStateAndRef), but this check happened before the ref was set to true. React state updates are asynchronous, so same-tick submissions could slip through:

// Before (race condition):
if (isRespondingRef.current || streamingState === StreamingState.Responding) {
  return; // Check happens before ref is set
}
// ... later in submitQuery:
setIsResponding(true); // Async state update - not effective for same-tick blocks

The Solution:

  1. Split isRespondingRef from React state - now a standalone useRef(false)
  2. Create setRespondingState callback that updates both synchronously:
const isRespondingRef = useRef(false);
const [isResponding, setIsResponding] = useState(false);

const setRespondingState = useCallback((value: boolean) => {
  isRespondingRef.current = value;  // Synchronous ref update
  setIsResponding(value);              // React state update
}, []);
  1. Set the ref immediately after the guard check in submitQuery:
if (
  (isRespondingRef.current || streamingState === StreamingState.Responding ||
    streamingState === StreamingState.WaitingForConfirmation) &&
  !options?.isContinuation
) {
  return;
}
+
+isRespondingRef.current = true;  // Synchronous block for same-tick submissions
+setIsResponding(true);
  1. Removed isRespondingRef check in handleRetryAttempt to allow retries:
// Before: blocked retries if turn cancelled or not responding
if (turnCancelledRef.current || !isRespondingRef.current) {
  return;
}

// After: always show retry status
setRetryStatus(payload);

Bundled Cleanup (massive scope):

Deleted subsystems:

  • ACP (Agent Control Panel): Entire command system removed (~1.5k lines)
  • Gemma commands: Setup, logs, platform detection removed (~1.5k lines)
  • Performance tests: Complete test suite removed (~1k lines)
  • Skills: Multiple skills removed (async-pr-review, behavioral-evals, string-reviewer, etc.)
  • GitHub workflows: Workflows for evals, docs-audit, agent-session-drift-check removed
  • Build scripts: Multiple build and utility scripts removed

CLI startup simplification:

  • Removed parent/daemon process pattern that auto-configured memory
  • Direct call to main() from entry point
  • Added --no-warnings=DEP0040 to suppress deprecation warnings

Issues

PR scope — critical — Bundles unrelated massive cleanup with focused bug fix

The PR mixes two completely different changes:

  1. Core bug fix: ~200 lines in useGeminiStream.ts to fix synchronous blocking
  2. Massive cleanup: 1,751 other files changed (1M+ deletions) removing entire features

Impact:

  • Impossible to review thoroughly in one pass
  • CI will take longer due to massive diff
  • Merge has high risk of breaking unrelated features
  • Rollback is difficult if cleanup introduces issues

Recommendation: Split into two separate PRs:

  1. PR docs: Add setup instructions for API key to README #1 (bug fix only): Focus on useGeminiStream.ts changes

    • Add isRespondingRef as standalone ref
    • Add setRespondingState callback
    • Update submitQuery guard logic
    • Remove isRespondingRef check in handleRetryAttempt
    • This is the stated fix and should be merged first
  2. PR Improve readability issues #2 (cleanup): Feature removal and startup simplification

    • Remove ACP commands
    • Remove gemma commands
    • Remove performance tests
    • Remove unused skills
    • Simplify CLI startup
    • Each subsystem removal should ideally be its own PR for easier review and rollback

packages/cli/src/ui/hooks/useGeminiStream.ts — good — Correct use of ref pattern

The core fix correctly addresses the race condition:

// Good: Synchronous ref update immediately after guard check
if (
  (isRespondingRef.current || streamingState === StreamingState.Responding ||
    streamingState === StreamingState.WaitingForConfirmation) &&
  !options?.isContinuation
) {
  return;
}
+
+isRespondingRef.current = true;  // Blocks same-tick submissions
+setIsResponding(true);

This ensures that even if React hasn't processed the state update yet, the ref check will block duplicate submissions.

setRespondingState callback — minor — Could be inlined

The setRespondingState callback is only used in one place (submitQuery). Consider inlining:

// Current pattern:
const setRespondingState = useCallback((value: boolean) => {
  isRespondingRef.current = value;
  setIsResponding(value);
}, []);

// Simpler alternative (if only used once):
// Just set both directly where needed

However, the callback pattern is fine if it's used in multiple places (the diff shows it's passed to useToolScheduler, so it's likely used elsewhere).

handleRetryAttempt change — warning — May show retries when inappropriate

Removing the isRespondingRef.current check in handleRetryAttempt means retry notifications will always show, even when:

// Before: blocked retries if not responding
if (turnCancelledRef.current || !isRespondingRef.current) {
  return;  // Don't show retry if we're not responding
}

This change appears intentional to allow showing retry status, but it could lead to UI flicker or confusing retry notifications after the stream has completed. Consider adding back a more targeted check:

if (turnCancelledRef.current) {
  return;  // Still block if turn was cancelled
}
// Allow retry notifications even if not currently responding
setRetryStatus(payload);

Cleanup scope — warning — No backward compatibility assessment

The massive cleanup removes:

  • ACP commands: Users relying on /acp commands will break
  • Gemma setup: Users running local gemma models via CLI will break
  • Performance tests: Developers using perf-tests for regression testing will break
  • Skills: Custom skills that depended on removed skills (e.g., async-pr-review) will break

Missing:

  • No migration guide for affected users
  • No deprecation notice (if this is a breaking change)
  • No changelog entry documenting removed features

CLI startup change — warning — Removes auto memory configuration

The parent/daemon process pattern that auto-configured memory (--max-old-space-size) has been removed:

// Before (removed):
async function getMemoryNodeArgs(): Promise<string[]> {
  // ... calculated targetMaxOldSpaceSizeInMB ...
  return [`--max-old-space-size=${targetMaxOldSpaceSizeInMB}`];
}

// After: Direct call to main()
import { main } from './src/gemini.js';
main().catch(...);

Impact:

  • Users on systems with limited memory may experience out-of-memory errors that were previously handled
  • The --no-warnings=DEP0040 flag suggests this change was made to address a deprecation warning, but the root cause should be investigated

Recommendation: Either:

  1. Keep auto memory configuration in the simplified startup
  2. Document that users should manually configure Node.js memory limits
  3. Add a warning on startup if system memory is low

Testing — critical — No tests added for race condition fix

The core fix addresses a race condition, but there are no tests added to verify:

  1. Same-tick submissions are blocked
  2. Retry notifications work correctly
  3. No UI flicker or duplicate submissions occur

Suggested test:

it('should block same-tick submissions', async () => {
  const { result } = renderHook(() => useGeminiStream(...));
  const submitQuery = result.current.submitQuery;
  
  // Submit query twice in same tick
  submitQuery('query1');
  submitQuery('query2');
  
  await waitFor(() => expect(isRespondingRef.current).toBe(true));
  // Verify only one submission was processed
  expect(activeQueryIdRef.current).toBeDefined();
});

Documentation — warning — No changelog or migration guide

The PR removes entire features (ACP, gemma, perf tests) but adds no documentation about:

  • What features are being removed and why
  • How users should migrate from removed features
  • What alternatives exist for removed functionality

Cross-file Impact

  • Breaking change: ACP commands removed - users with /acp in workflows will break
  • Breaking change: Gemma commands removed - local model users need alternative setup
  • Performance impact: Simplified CLI startup may be faster (~1.5s per the removed code), but auto memory configuration is lost
  • UI impact: Retry notifications may appear more frequently (removed isRespondingRef check)
  • Developer experience: Removed perf-tests and many skills will affect contributors

Assessment

⚠️ Request changes

The core fix (synchronous blocking with isRespondingRef) is well-implemented and addresses a legitimate race condition. However, the PR is severely scoped incorrectly by bundling this focused fix with massive, unrelated cleanup (1,951 files changed, 1M+ deletions).

Required changes:

  1. Split the PR into two separate PRs:

  2. Add tests for the race condition fix to verify same-tick submissions are blocked

  3. Add documentation for the cleanup:

    • Changelog entry documenting removed features
    • Migration guide for affected users
    • Explanation of why features were removed
  4. Assess CLI startup change: Determine if auto memory configuration can be preserved in the simplified startup, or document the manual configuration requirement

Once split into appropriate PRs:

This approach allows for:

  • Faster iteration on the bug fix
  • Safer rollout of cleanup changes
  • Better review coverage for each change
  • Easier rollback if issues arise

@spencer426

Copy link
Copy Markdown
Contributor

Thank you for your interest in contributing to the project! We are closing this PR as the associated issue was closed.

@spencer426 spencer426 closed this May 7, 2026
@sripasg sripasg added the size/m A medium sized PR label Jun 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/core Issues related to User Interface, OS Support, Core Functionality help wanted We will accept PRs from all issues marked as "help wanted". Thanks for your support! priority/p1 Important and should be addressed in the near term. size/m A medium sized PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: loop retry race condition when user clicks disable

6 participants