Skip to content

test(cleanup): fix temporary directory leaks in test suites#26217

Merged
Adib234 merged 5 commits intomainfrom
fix/test-temp-cleanup
May 4, 2026
Merged

test(cleanup): fix temporary directory leaks in test suites#26217
Adib234 merged 5 commits intomainfrom
fix/test-temp-cleanup

Conversation

@Adib234
Copy link
Copy Markdown
Contributor

@Adib234 Adib234 commented Apr 29, 2026

Summary

This PR fixes an issue where several test suites were leaving behind temporary directories in the OS temp folder, leading to disk space waste and clutter.

Details

  • Updated packages/core/src/tools/mcp-client.test.ts (4 locations), packages/cli/src/config/extension-manager-scope.test.ts, and packages/cli/src/commands/extensions/configure.test.ts to include proper afterEach cleanup logic.
  • The cleanup logic uses fs.rmSync(..., { recursive: true, force: true }) and includes a 100ms delay for Windows to ensure file handles are released.
  • workspaceContext references are cleared to prevent potential memory leaks.
  • configure.test.ts was updated to use path.join(os.tmpdir(), ...) for consistency.

Related Issues

Fixes #22032

How to Validate

  1. Run the affected tests:
    • npm test -w @google/gemini-cli-core -- src/tools/mcp-client.test.ts
    • npm test -w @google/gemini-cli -- src/config/extension-manager-scope.test.ts
    • npm test -w @google/gemini-cli -- src/commands/extensions/configure.test.ts
  2. Check the OS temporary directory (e.g., /tmp on MacOS/Linux) and verify that no gemini-agent-test-* or gemini-cli-test-* directories remain after the tests complete.

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

Add proper afterEach cleanup logic to mcp-client.test.ts, extension-manager-scope.test.ts, and configure.test.ts to ensure temporary directories are removed after test completion.

Fixes #22032
@Adib234 Adib234 requested review from a team as code owners April 29, 2026 19:35
@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 addresses persistent issues where test suites were failing to clean up temporary directories, resulting in significant disk space usage and clutter. By standardizing the teardown process and ensuring proper resource release, this change improves the reliability and cleanliness of the test environment across different operating systems.

Highlights

  • Temporary Directory Cleanup: Implemented robust afterEach cleanup logic across multiple test suites to ensure temporary directories are removed after test execution, preventing disk clutter.
  • Cross-Platform Compatibility: Added a 100ms delay for Windows environments in the cleanup logic to ensure file handles are properly released before deletion.
  • Memory Leak Prevention: Explicitly cleared workspaceContext references in test teardowns to avoid potential memory leaks.
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.

@gemini-cli gemini-cli Bot added the area/core Issues related to User Interface, OS Support, Core Functionality label Apr 29, 2026
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 introduces cleanup logic in the test suites to address temporary directory leaks. The reviewer identified unrelated changes in package-lock.json that should be reverted to maintain focus. Additionally, the reviewer recommended refactoring the manual platform-specific timeouts in the cleanup code to use the built-in maxRetries and retryDelay options of fs.rmSync for improved robustness.

I am having trouble creating individual review comments. Click here to see my feedback.

package-lock.json (453)

high

The package-lock.json contains numerous unrelated changes adding "peer": true to various dependencies. These changes appear to be a side effect of using a different npm version or environment and are unrelated to the PR's goal of fixing temporary directory leaks. Please revert these changes to keep the PR focused and avoid potential issues with dependency resolution in other environments.

References
  1. Pull Requests should be kept small and focused. (link)

packages/cli/src/commands/extensions/configure.test.ts (101-111)

high

Instead of a manual setTimeout and platform check, you can use the built-in maxRetries and retryDelay options in fs.rmSync. This is more robust, cleaner, and avoids a mandatory delay on every test run. It also allows the afterEach to be synchronous, which is preferred in test environments for simplicity.

  afterEach(() => {
    try {
      if (tempWorkspaceDir) {
        fs.rmSync(tempWorkspaceDir, {
          recursive: true,
          force: true,
          maxRetries: 10,
          retryDelay: 100,
        });
      }
    } catch {
      // ignore
    }
  });
References
  1. For test environment setup, it is acceptable to use synchronous operations (e.g., fs.*Sync, execSync) within an async function. This approach is preferred for its simplicity and readability when the setup is sequential and not a performance bottleneck.

packages/cli/src/config/extension-manager-scope.test.ts (90-102)

high

Instead of a manual setTimeout and platform check, you can use the built-in maxRetries and retryDelay options in fs.rmSync. This is more robust, cleaner, and avoids a mandatory delay on every test run. It also allows the afterEach to be synchronous, which is preferred in test environments for simplicity.

  afterEach(() => {
    for (const dir of [currentTempHome, tempWorkspace]) {
      try {
        if (dir) {
          fs.rmSync(dir, {
            recursive: true,
            force: true,
            maxRetries: 10,
            retryDelay: 100,
          });
        }
      } catch {
        // ignore
      }
    }
  });
References
  1. For test environment setup, it is acceptable to use synchronous operations (e.g., fs.*Sync, execSync) within an async function. This approach is preferred for its simplicity and readability when the setup is sequential and not a performance bottleneck.

packages/core/src/tools/mcp-client.test.ts (108-118)

high

Instead of a manual setTimeout and platform check, you can use the built-in maxRetries and retryDelay options in fs.rmSync. This is more robust, cleaner, and avoids a mandatory delay on every test run. It also allows the afterEach to be synchronous, which is preferred in test environments for simplicity.

  afterEach(() => {
    try {
      if (testWorkspace) {
        fs.rmSync(testWorkspace, {
          recursive: true,
          force: true,
          maxRetries: 10,
          retryDelay: 100,
        });
      }
    } catch {
      // ignore
    }
  });
References
  1. For test environment setup, it is acceptable to use synchronous operations (e.g., fs.*Sync, execSync) within an async function. This approach is preferred for its simplicity and readability when the setup is sequential and not a performance bottleneck.

packages/core/src/tools/mcp-client.test.ts (2424-2434)

high

Instead of a manual setTimeout and platform check, you can use the built-in maxRetries and retryDelay options in fs.rmSync. This is more robust, cleaner, and avoids a mandatory delay on every test run. It also allows the afterEach to be synchronous, which is preferred in test environments for simplicity.

  afterEach(() => {
    try {
      if (testWorkspace) {
        fs.rmSync(testWorkspace, {
          recursive: true,
          force: true,
          maxRetries: 10,
          retryDelay: 100,
        });
      }
    } catch {
      // ignore
    }
  });
References
  1. For test environment setup, it is acceptable to use synchronous operations (e.g., fs.*Sync, execSync) within an async function. This approach is preferred for its simplicity and readability when the setup is sequential and not a performance bottleneck.

packages/core/src/tools/mcp-client.test.ts (2642-2652)

high

Instead of a manual setTimeout and platform check, you can use the built-in maxRetries and retryDelay options in fs.rmSync. This is more robust, cleaner, and avoids a mandatory delay on every test run. It also allows the afterEach to be synchronous, which is preferred in test environments for simplicity.

  afterEach(() => {
    try {
      if (testWorkspace) {
        fs.rmSync(testWorkspace, {
          recursive: true,
          force: true,
          maxRetries: 10,
          retryDelay: 100,
        });
      }
    } catch {
      // ignore
    }
  });
References
  1. For test environment setup, it is acceptable to use synchronous operations (e.g., fs.*Sync, execSync) within an async function. This approach is preferred for its simplicity and readability when the setup is sequential and not a performance bottleneck.

packages/core/src/tools/mcp-client.test.ts (2816-2826)

high

Instead of a manual setTimeout and platform check, you can use the built-in maxRetries and retryDelay options in fs.rmSync. This is more robust, cleaner, and avoids a mandatory delay on every test run. It also allows the afterEach to be synchronous, which is preferred in test environments for simplicity.

  afterEach(() => {
    try {
      if (testWorkspace) {
        fs.rmSync(testWorkspace, {
          recursive: true,
          force: true,
          maxRetries: 10,
          retryDelay: 100,
        });
      }
    } catch {
      // ignore
    }
  });
References
  1. For test environment setup, it is acceptable to use synchronous operations (e.g., fs.*Sync, execSync) within an async function. This approach is preferred for its simplicity and readability when the setup is sequential and not a performance bottleneck.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 29, 2026

Size Change: -4 B (0%)

Total Size: 33.9 MB

Filename Size Change
./bundle/chunk-236ONPCG.js 0 B -12.5 kB (removed) 🏆
./bundle/chunk-4EILQ74Z.js 0 B -19.5 kB (removed) 🏆
./bundle/chunk-64XHI5ON.js 0 B -3.8 kB (removed) 🏆
./bundle/chunk-A3DTGHGI.js 0 B -657 kB (removed) 🏆
./bundle/chunk-FFPFMQ76.js 0 B -49.2 kB (removed) 🏆
./bundle/chunk-G4FLWYLL.js 0 B -2.72 MB (removed) 🏆
./bundle/chunk-SXXFYGHX.js 0 B -14.7 MB (removed) 🏆
./bundle/chunk-YNIVRW3V.js 0 B -3.43 kB (removed) 🏆
./bundle/core-EMNXNZTC.js 0 B -48.4 kB (removed) 🏆
./bundle/devtoolsService-AGZHIDMZ.js 0 B -28 kB (removed) 🏆
./bundle/gemini-VEXUSHPP.js 0 B -582 kB (removed) 🏆
./bundle/interactiveCli-BLQ7V3QW.js 0 B -1.32 MB (removed) 🏆
./bundle/liteRtServerManager-DQ2ASCG3.js 0 B -2.11 kB (removed) 🏆
./bundle/oauth2-provider-R7BAVZ3M.js 0 B -9.16 kB (removed) 🏆
./bundle/chunk-4CC52H6M.js 657 kB +657 kB (new file) 🆕
./bundle/chunk-4OVQNPGN.js 12.5 kB +12.5 kB (new file) 🆕
./bundle/chunk-DMWAOXCG.js 49.2 kB +49.2 kB (new file) 🆕
./bundle/chunk-HYZNYPFQ.js 3.43 kB +3.43 kB (new file) 🆕
./bundle/chunk-KJZICOXH.js 19.5 kB +19.5 kB (new file) 🆕
./bundle/chunk-LDSQ6QQO.js 14.7 MB +14.7 MB (new file) 🆕
./bundle/chunk-LLOTGAOI.js 3.8 kB +3.8 kB (new file) 🆕
./bundle/chunk-QP3CPPSM.js 2.72 MB +2.72 MB (new file) 🆕
./bundle/core-R52FQNUH.js 48.4 kB +48.4 kB (new file) 🆕
./bundle/devtoolsService-CVNSP6Q6.js 28 kB +28 kB (new file) 🆕
./bundle/gemini-UV6ZPPKW.js 582 kB +582 kB (new file) 🆕
./bundle/interactiveCli-ZX77WZZS.js 1.32 MB +1.32 MB (new file) 🆕
./bundle/liteRtServerManager-4O5KO3QV.js 2.11 kB +2.11 kB (new file) 🆕
./bundle/oauth2-provider-RKQJWX2C.js 9.16 kB +9.16 kB (new file) 🆕
ℹ️ View Unchanged
Filename Size Change
./bundle/bundled/third_party/index.js 8 MB 0 B
./bundle/chunk-34MYV7JD.js 2.45 kB 0 B
./bundle/chunk-5AUYMPVF.js 858 B 0 B
./bundle/chunk-5PS3AYFU.js 1.18 kB 0 B
./bundle/chunk-664ZODQF.js 124 kB 0 B
./bundle/chunk-DAHVX5MI.js 206 kB 0 B
./bundle/chunk-DD4MWEAB.js 1.97 MB 0 B
./bundle/chunk-IUUIT4SU.js 56.5 kB 0 B
./bundle/chunk-RJTRUG2J.js 39.8 kB 0 B
./bundle/cleanup-Y6R3I7CJ.js 0 B -932 B (removed) 🏆
./bundle/devtools-36NN55EP.js 696 kB 0 B
./bundle/dist-T73EYRDX.js 356 B 0 B
./bundle/events-XB7DADIJ.js 418 B 0 B
./bundle/examples/hooks/scripts/on-start.js 188 B 0 B
./bundle/examples/mcp-server/example.js 1.43 kB 0 B
./bundle/gemini.js 5.1 kB 0 B
./bundle/getMachineId-bsd-TXG52NKR.js 1.55 kB 0 B
./bundle/getMachineId-darwin-7OE4DDZ6.js 1.55 kB 0 B
./bundle/getMachineId-linux-SHIFKOOX.js 1.34 kB 0 B
./bundle/getMachineId-unsupported-5U5DOEYY.js 1.06 kB 0 B
./bundle/getMachineId-win-6KLLGOI4.js 1.72 kB 0 B
./bundle/memoryDiscovery-HRURE3F3.js 980 B 0 B
./bundle/multipart-parser-KPBZEGQU.js 11.7 kB 0 B
./bundle/node_modules/@google/gemini-cli-devtools/dist/client/main.js 222 kB 0 B
./bundle/node_modules/@google/gemini-cli-devtools/dist/src/_client-assets.js 229 kB 0 B
./bundle/node_modules/@google/gemini-cli-devtools/dist/src/index.js 13.4 kB 0 B
./bundle/node_modules/@google/gemini-cli-devtools/dist/src/types.js 132 B 0 B
./bundle/sandbox-macos-permissive-open.sb 890 B 0 B
./bundle/sandbox-macos-permissive-proxied.sb 1.31 kB 0 B
./bundle/sandbox-macos-restrictive-open.sb 3.36 kB 0 B
./bundle/sandbox-macos-restrictive-proxied.sb 3.56 kB 0 B
./bundle/sandbox-macos-strict-open.sb 4.82 kB 0 B
./bundle/sandbox-macos-strict-proxied.sb 5.02 kB 0 B
./bundle/src-QVCVGIUX.js 47 kB 0 B
./bundle/start-HDRWHW6K.js 0 B -652 B (removed) 🏆
./bundle/tree-sitter-7U6MW5PS.js 274 kB 0 B
./bundle/tree-sitter-bash-34ZGLXVX.js 1.84 MB 0 B
./bundle/cleanup-7IKDZEMZ.js 932 B +932 B (new file) 🆕
./bundle/start-EEFQSQ43.js 652 B +652 B (new file) 🆕

compressed-size-action

Moving vi.useRealTimers() to the top of afterEach hooks ensures that any async operations (like microtasks or the Windows file handle delay) don't hang when fake timers were active in the test.
Copy link
Copy Markdown
Contributor

@devr0306 devr0306 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

Summary
The PR correctly addresses the temporary folder leak by explicitly routing fs.mkdtempSync to os.tmpdir() and adding explicit teardown logic in the afterEach hooks. The inclusion of vi.useRealTimers() prior to the async teardown is an excellent catch to prevent tests from hanging on the Windows setTimeout delay when fake timers are active.

Findings

  • Improvement (Code Duplication):
    You have duplicated the exact same robust cleanup block (the Windows platform check, the 100ms setTimeout, and the fs.rmSync inside a try/catch) roughly 6 times across configure.test.ts, extension-manager-scope.test.ts, and mcp-client.test.ts.

    The codebase already has a centralized test utility for this: cleanupTmpDir in packages/test-utils/src/file-system-test-helpers.ts. Currently, it only does:

    export async function cleanupTmpDir(dir: string) {
      await fs.rm(dir, { recursive: true, force: true });
    }

    I highly recommend updating cleanupTmpDir in the test-utils package to include your new Windows delay and try/catch resilience, and then simply importing and calling await cleanupTmpDir(testWorkspace) in your afterEach blocks. This will keep the test files much cleaner and ensure the Windows fix is applied universally whenever anyone cleans up a temp directory.

  • Nitpick:
    You are using fs.rmSync inside an async function (afterEach(async () => { ... })). If you extract this to the async cleanupTmpDir helper, you can safely use the async fs.rm (from node:fs/promises) instead to avoid blocking the event loop during teardowns.

Conclusion
The core logic is correct and effectively fixes the bug and test hangs. However, before merging, consider consolidating the duplicate cleanup logic into the existing cleanupTmpDir utility in packages/test-utils for better maintainability.

Centralized the temporary directory cleanup logic into test-utils/cleanupTmpDir. This utility now includes a 100ms delay for Windows platform and handles errors silently. Updated all affected test files to use this new utility.
Copy link
Copy Markdown
Contributor

@devr0306 devr0306 left a comment

Choose a reason for hiding this comment

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

LGTM

@Adib234 Adib234 enabled auto-merge April 30, 2026 18:34
@Adib234 Adib234 added this pull request to the merge queue May 4, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks May 4, 2026
@Adib234 Adib234 added this pull request to the merge queue May 4, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks May 4, 2026
@Adib234 Adib234 added this pull request to the merge queue May 4, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks May 4, 2026
@Adib234 Adib234 added this pull request to the merge queue May 4, 2026
Merged via the queue into main with commit 75a8de8 May 4, 2026
27 checks passed
@Adib234 Adib234 deleted the fix/test-temp-cleanup branch May 4, 2026 19:20
TirthNaik-99 pushed a commit to TirthNaik-99/gemini-cli that referenced this pull request May 4, 2026
kimjune01 pushed a commit to kimjune01/gemini-cli-claude that referenced this pull request May 6, 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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Test suites leave behind temporary directories causing disk space waste

3 participants