Skip to content

fix(core): address memory leaks in oauth flow, chat history, and shell parsing#24963

Closed
spencer426 wants to merge 5 commits intomainfrom
fix/memory-leaks
Closed

fix(core): address memory leaks in oauth flow, chat history, and shell parsing#24963
spencer426 wants to merge 5 commits intomainfrom
fix/memory-leaks

Conversation

@spencer426
Copy link
Copy Markdown
Contributor

Summary

Addresses four distinct memory leaks identified in the memory trace, eliminating unbounded growth and closure retention issues that caused gigabytes of memory consumption over time.

Details

  1. OAuth 5-Minute Timeout Leak: In packages/core/src/code_assist/oauth2.ts and packages/core/src/utils/oauth-flow.ts, 5-minute timeouts were being created and never cleared upon success. This forced the garbage collector to retain the overarching Generator loop, pending Promises, and AsyncLocalStorage telemetry spans for the full 5 minutes. The fix explicitly tracks the timeout and clears it using a .finally() block.
  2. ChatRecordingService Unbounded Growth: ChatRecordingService was holding ~350MB on its own because the historyManager array grew indefinitely without any truncation mechanism. The fix enforces a hard limit of 1000 items via an LRU-style slice in useHistoryManager.ts.
  3. Tree-Sitter Loader Hanging Promise: The treeSitterInitialization Promise in shell-utils.ts could hang indefinitely if the native WASM bindings failed to initialize, leaking the module execution context. The fix wraps it in an aggressive 5000ms Promise.race timeout that rejects safely on failure.
  4. Stale React Fiber Closure: In useAtCompletion.ts, resetFileSearchState captured an initial stale closure to the Ink/React FiberNodes and held it forever in the global workspace context event listener. Wrapping the function in useCallback ensures a stable, non-stale closure.

(Note: A claim in the memory trace about vimHandleInput closures leaking was verified to be a misdiagnosis, as the handlers are correctly deregistered.)

Related Issues

How to Validate

  1. Start a local OAuth callback server flow to verify the timeout is properly cleared immediately:

    Create a file test-timeout.ts:

    import { startCallbackServer } from './packages/core/src/utils/oauth-flow.ts';
    
    async function runTest() {
      console.log('Starting OAuth callback server with a 5-minute timeout...');
      const { port, response } = startCallbackServer('test-state-123');
      
      const assignedPort = await port;
      console.log(`Server listening on port ${assignedPort}.`);
    
      console.log('Simulating successful browser redirect...');
      await fetch(`http://localhost:${assignedPort}/oauth/callback?code=test-code-abc&state=test-state-123`);
    
      const result = await response;
      console.log('Authentication flow completed successfully with code:', result.code);
      console.log('Script finished. If the bug is fixed, the process will exit IMMEDIATELY.');
    }
    
    runTest().catch(console.error);
  2. Execute the script:

    npx tsx test-timeout.ts
  3. Ensure the script prints the output and exits instantaneously without hanging.

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

@spencer426 spencer426 requested a review from a team as a code owner April 8, 2026 20:25
@gemini-cli
Copy link
Copy Markdown
Contributor

gemini-cli Bot commented Apr 8, 2026

Hi @spencer426, thank you so much for your contribution to Gemini CLI! We really appreciate the time and effort you've put into this.

We're making some updates to our contribution process to improve how we track and review changes. Please take a moment to review our recent discussion post: Improving Our Contribution Process & Introducing New Guidelines.

Key Update: Starting January 26, 2026, the Gemini CLI project will require all pull requests to be associated with an existing issue. Any pull requests not linked to an issue by that date will be automatically closed.

Thank you for your understanding and for being a part of our community!

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 8, 2026

Size Change: +1.1 kB (0%)

Total Size: 34 MB

Filename Size Change
./bundle/chunk-6XX6OGDS.js 0 B -3.16 MB (removed) 🏆
./bundle/chunk-COROWTX4.js 0 B -14.8 MB (removed) 🏆
./bundle/core-HTSUUI7S.js 0 B -45.5 kB (removed) 🏆
./bundle/devtoolsService-UGLGCZV3.js 0 B -28.4 kB (removed) 🏆
./bundle/interactiveCli-KHINQZSC.js 0 B -1.65 MB (removed) 🏆
./bundle/oauth2-provider-OELU3DIV.js 0 B -9.16 kB (removed) 🏆
./bundle/chunk-7V4NTKYY.js 14.8 MB +14.8 MB (new file) 🆕
./bundle/chunk-ZN3XY2WQ.js 3.16 MB +3.16 MB (new file) 🆕
./bundle/core-KO4XW4H7.js 45.5 kB +45.5 kB (new file) 🆕
./bundle/devtoolsService-5ORE47TK.js 28.4 kB +28.4 kB (new file) 🆕
./bundle/interactiveCli-4POPG3VA.js 1.65 MB +1.65 MB (new file) 🆕
./bundle/oauth2-provider-Z5TKQEM4.js 9.16 kB +9.16 kB (new file) 🆕
ℹ️ View Unchanged
Filename Size
./bundle/bundled/third_party/index.js 8 MB
./bundle/chunk-34MYV7JD.js 2.45 kB
./bundle/chunk-5AUYMPVF.js 858 B
./bundle/chunk-5PS3AYFU.js 1.18 kB
./bundle/chunk-664ZODQF.js 124 kB
./bundle/chunk-DAHVX5MI.js 206 kB
./bundle/chunk-IUUIT4SU.js 56.5 kB
./bundle/chunk-OGWWODAT.js 1.96 MB
./bundle/chunk-RJTRUG2J.js 39.8 kB
./bundle/devtools-36NN55EP.js 696 kB
./bundle/dist-T73EYRDX.js 356 B
./bundle/events-XB7DADIJ.js 418 B
./bundle/gemini.js 554 kB
./bundle/getMachineId-bsd-TXG52NKR.js 1.55 kB
./bundle/getMachineId-darwin-7OE4DDZ6.js 1.55 kB
./bundle/getMachineId-linux-SHIFKOOX.js 1.34 kB
./bundle/getMachineId-unsupported-5U5DOEYY.js 1.06 kB
./bundle/getMachineId-win-6KLLGOI4.js 1.72 kB
./bundle/memoryDiscovery-JNNGTYL3.js 980 B
./bundle/multipart-parser-KPBZEGQU.js 11.7 kB
./bundle/node_modules/@google/gemini-cli-devtools/dist/client/main.js 222 kB
./bundle/node_modules/@google/gemini-cli-devtools/dist/src/_client-assets.js 229 kB
./bundle/node_modules/@google/gemini-cli-devtools/dist/src/index.js 13.4 kB
./bundle/node_modules/@google/gemini-cli-devtools/dist/src/types.js 132 B
./bundle/sandbox-macos-permissive-open.sb 890 B
./bundle/sandbox-macos-permissive-proxied.sb 1.31 kB
./bundle/sandbox-macos-restrictive-open.sb 3.36 kB
./bundle/sandbox-macos-restrictive-proxied.sb 3.56 kB
./bundle/sandbox-macos-strict-open.sb 4.82 kB
./bundle/sandbox-macos-strict-proxied.sb 5.02 kB
./bundle/src-QVCVGIUX.js 47 kB
./bundle/tree-sitter-7U6MW5PS.js 274 kB
./bundle/tree-sitter-bash-34ZGLXVX.js 1.84 MB

compressed-size-action

@gemini-cli gemini-cli Bot added the status/need-issue Pull requests that need to have an associated issue. label Apr 8, 2026
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, 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 significantly improves memory management and application stability by addressing several distinct memory leaks. It targets issues ranging from uncleared timeouts in the OAuth flow and unbounded growth in chat history to hanging promises during Tree-sitter initialization and stale React closures. The changes collectively eliminate sources of excessive memory consumption and resource retention, leading to a more robust and efficient application.

Highlights

  • OAuth Timeout Leak Fixed: Addressed a memory leak in the OAuth authentication flow where 5-minute timeouts were not being cleared upon success, leading to retained generator loops and pending Promises.
  • Chat History Growth Limited: Implemented a hard limit of 1000 items for the ChatRecordingService history to prevent unbounded memory growth, which previously consumed significant memory.
  • Tree-Sitter Promise Leak Resolved: Fixed a potential leak where the treeSitterInitialization Promise could hang indefinitely if WASM bindings failed, by wrapping it in a 5000ms Promise.race timeout.
  • Stale React Closure Prevented: Corrected a stale React Fiber closure issue in useAtCompletion.ts by wrapping resetFileSearchState in useCallback, ensuring a stable and non-stale closure.
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.

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.

Copy link
Copy Markdown
Contributor

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

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 implements several memory management and stability improvements across the CLI and core packages. Key changes include memoizing the resetFileSearchState hook to prevent unnecessary effect triggers, capping the UI history size to 1000 items, and ensuring that authentication timeouts are consistently cleared using finally blocks. Review feedback identifies that the history truncation should also be applied to the underlying ChatRecordingService using a standard LruCache and points out a memory leak in the shell parser initialization where the timeout was not cleared upon success.

Comment on lines +88 to +90
if (newHistory.length > 1000) {
return newHistory.slice(newHistory.length - 1000);
}
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.

high

While this change limits the history in the UI state, it does not address the unbounded growth in the ChatRecordingService itself, which was identified as the source of the ~350MB leak. The recordMessage method continues to append to an internal array without truncation. To resolve this, the messages state should be managed using the repository's standard LruCache implementation to prevent memory issues and ensure bounded growth. When implementing truncation, prefer a simple approach over complex logic if the inaccuracy is trivial.

References
  1. Avoid module-level global variables for state like caches to prevent race conditions and memory issues. Use session-scoped or instance-scoped state and leverage standard cache implementations like LRUCache.
  2. For caching, use the existing LruCache dependency instead of clearing the entire cache or implementing a custom LRU policy.
  3. When implementing truncation logic, a simpler approach is preferred over a more complex one if the potential inaccuracy is trivial compared to the overall buffer size.

Comment on lines +167 to +176
let timerId: NodeJS.Timeout | undefined;
const timeoutPromise = new Promise<void>((_, reject) => {
timerId = setTimeout(
() => reject(new Error('Tree-sitter initialization timed out')),
5000,
);
});
treeSitterInitialization = Promise.race([
loadBashLanguage(),
timeoutPromise,
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.

high

The setTimeout for tree-sitter initialization is not cleared if the operation succeeds, creating a memory leak. Also, the 5-second timeout might be too short for some environments, leading to premature failures. Furthermore, avoid using the treeSitterInitialization promise object itself to track operation state; use an explicit state variable instead.

    let timeoutId: NodeJS.Timeout | undefined;
    const timeoutPromise = new Promise<void>((_, reject) => {
      timeoutId = setTimeout(
        () => reject(new Error('Tree-sitter initialization timed out')),
        5000,
      );
    });
    treeSitterInitialization = Promise.race([
      loadBashLanguage(),
      timeoutPromise,
    ])
      .finally(() => {
        if (timeoutId) clearTimeout(timeoutId);
      })
      .catch((error) => {
References
  1. Do not blindly apply short timeouts (like 5 seconds) to operations, as they may be too short and cause operations to abort prematurely.
  2. When managing the state of asynchronous operations, rely on an explicit state variable rather than checking for the existence of a promise object.

@spencer426
Copy link
Copy Markdown
Contributor Author

Closing this monolithic PR in favor of four separate atomic PRs to make review, testing, and potential rollback easier for maintainers:

  1. OAuth timeout cleanup: fix(core): clear 5-minute timeouts in oauth flow to prevent memory leaks #24968
  2. Chat history cap: fix(core): cap chat history at 1000 items to prevent unbounded memory growth #24969
  3. Shell parser timeout: fix(core): add timeout to tree-sitter initialization to prevent hanging #24970
  4. Hook closure stabilization: fix(cli): stabilize resetFileSearchState closure in useAtCompletion #24971

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status/need-issue Pull requests that need to have an associated issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant