Skip to content

fix(cli): skip eager recursive file reading for @<directory> references - #29617

Open
jvargassanchez-dot wants to merge 3 commits into
google-gemini:mainfrom
jvargassanchez-dot:b_568410399
Open

jvargassanchez-dot wants to merge 3 commits into
google-gemini:mainfrom
jvargassanchez-dot:b_568410399

Conversation

@jvargassanchez-dot

Copy link
Copy Markdown
Contributor

Summary

Updates @<path> command processing in atCommandProcessor.ts so that directory references (@<directory>) resolve to their relative workspace path without eagerly expanding into a recursive ** glob and inlining the contents of all files via ReadManyFilesTool. This allows the model to evaluate the user's request first and select the appropriate tool (such as list_directory, glob, grep_search, or read_file), significantly reducing prompt token usage when referencing directories.

Details

Root Cause

When a user referenced a directory using @<directory> (for example, List all files stored in @Documents/internal/), resolveFilePaths in packages/cli/src/ui/hooks/atCommandProcessor.ts converted the directory path into a recursive glob (path.join(relativePath, '**')) and passed it to readLocalFiles, which executed ReadManyFilesTool client-side prior to sending the prompt to the model. For directories containing many files or large nested subdirectories, this inlined the full text content of every file into the initial user prompt even when the user only requested a directory listing.

Key Changes

  • packages/cli/src/ui/hooks/atCommandProcessor.ts:
    • Added isDirectory?: boolean to ResolvedFile.
    • Updated resolveFilePaths to record directory paths with pathSpec: relativePath and isDirectory: true instead of rewriting them to path.join(relativePath, '**').
    • Updated readLocalFiles to filter out directory entries (resolvedFiles.filter((rf) => !rf.isDirectory)) before invoking ReadManyFilesTool, while preserving eager content reading for explicit @<file> references.
  • packages/cli/src/ui/hooks/atCommandProcessor.test.ts:
    • Updated directory resolution unit tests and added coverage for @<directory> prompts and mixed @<file> + @<directory> queries.

Related Issues

How to Validate

  • Ran unit tests in packages/cli/src/ui/hooks/atCommandProcessor.test.ts.
  • Ran npm run typecheck and npm run lint.

Pre-Merge Checklist

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

Resolve @<directory> references to their relative workspace path without converting them into recursive '**' globs for client-side ReadManyFilesTool execution. This prevents inlining the entire contents of all files in a directory into the initial prompt and allows the model to evaluate the user request and select the appropriate tool (e.g., list_directory or read_file), reducing unnecessary token usage.

- Add isDirectory flag to ResolvedFile in atCommandProcessor.ts.
- Keep relative directory pathSpec instead of expanding directories to '**' in resolveFilePaths.
- Filter out directory entries in readLocalFiles before invoking ReadManyFilesTool.
- Update and add unit tests in atCommandProcessor.test.ts for directory and mixed @<file> + @<directory> references.
@jvargassanchez-dot
jvargassanchez-dot requested a review from a team as a code owner October 2, 2026 19:27
@github-actions github-actions Bot added the size/m A medium sized PR label Oct 2, 2026
@github-actions

github-actions Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

📊 PR Size: size/M

  • Lines changed: 185
  • Additions: +134
  • Deletions: -51
  • Files changed: 3

@gemini-cli gemini-cli Bot added the priority/p1 Important and should be addressed in the near term. label Oct 2, 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 optimizes how the CLI processes directory references provided in user prompts. Previously, referencing a directory would cause the system to recursively expand and inline the contents of all files within that directory before sending the prompt to the model. The changes introduce a mechanism to identify directory references and exclude them from the eager file-reading process, allowing the model to interact with the directory structure more efficiently and reducing unnecessary prompt overhead.

Highlights

  • Directory Reference Handling: Updated the CLI to treat directory references (@<directory>) as distinct entities rather than expanding them into recursive globs, preventing unnecessary eager file reading.
  • Token Usage Optimization: By skipping the automatic inlining of directory contents, the model can now intelligently decide which tools to use (e.g., list_directory, grep_search) based on the user's request, significantly reducing prompt token consumption.
  • Refined Test Coverage: Updated existing unit tests and added new scenarios to verify that directory references are handled correctly without triggering eager reads, while maintaining standard behavior for individual file references.
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 the 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 counterproductive. 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-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 prevents eagerly reading all files when resolving directory paths (such as @<directory>) by introducing an optional isDirectory property to the ResolvedFile interface and filtering out directories before reading local files. Feedback is provided to explicitly handle the optional isDirectory property using the nullish coalescing operator (??) instead of relying on implicit falsiness, ensuring strict adherence to coding standards for optional properties.

Comment thread packages/cli/src/ui/hooks/atCommandProcessor.ts Outdated
@jvargassanchez-dot

Copy link
Copy Markdown
Contributor Author

/gemini review

@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 prevents the eager reading of all files when resolving directory paths (e.g., @<directory>) in the @ command processor. It introduces an isDirectory flag to the ResolvedFile interface, filters out directories from the list of files to be read locally, and updates the corresponding unit tests to verify this behavior. There are no review comments to address, and I have no feedback to provide.

@kschaab kschaab 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.

Summary

Thanks for putting this together! Skipping eager recursive ReadManyFilesTool reads on @<directory> references makes sense for avoiding context window blowups on large directories when the user only wants to list or search files.

Key Observations

  1. Edge Case with Workspace Root Directory (relativePath === ""): When an absolute path to the workspace root directory itself is referenced (e.g., @/path/to/workspace), resolveAtCommandPath returns relativePath: "" (from path.relative(dir, dir)). Previously path.join('', '**') turned this into '**', which was truthy. With pathSpec: relativePath, pathSpec becomes "", causing constructInitialQuery (const resolved = replacementMap.get(part); content = resolved ? ... : part.content) to treat resolved as falsy and leave the raw path un-normalized.
  2. Dead Glob Check in readLocalFiles: The rf.pathSpec.endsWith('**') check in readLocalFiles was only needed when directories were rewritten to path.join(relativePath, '**') and can now be simplified.
  3. Documentation & ACP Consistency:
    • User-facing docs in docs/reference/commands.md and the JSDoc on handleAtCommand still state that @<directory> reads all files in the directory and subdirectories via read_many_files.
    • packages/cli/src/acp/acpSession.ts has a parallel @<directory> handler that still expands directories to ${pathName}/** for ReadManyFilesTool—should ACP mode be updated to match?
    • Please also link the relevant GitHub issue under ## Related Issues in the PR description.

Comment on lines 303 to 313
if (stats.isDirectory()) {
const pathSpec = path.join(relativePath, '**');
resolvedFiles.push({
part,
pathSpec,
pathSpec: relativePath,
displayLabel: path.isAbsolute(pathName) ? relativePath : pathName,
absolutePath,
isDirectory: true,
});
onDebugMessage(
`Path ${pathName} resolved to directory, using glob: ${pathSpec}`,
`Path ${pathName} resolved to directory: ${absolutePath}, using relative path: ${relativePath}`,
);

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.

Edge Case & Duplication ([TEST-002], [DRY-001]): Handle empty relativePath for workspace root and consolidate if (stats.isDirectory()) / else branches

Issue:

  1. When pathName is an absolute path to the workspace root directory itself (pathName === dir), resolveAtCommandPath computes path.relative(dir, pathName) which is "" (empty string). Previously, path.join('', '**') produced '**' (truthy). Setting pathSpec: relativePath directly leaves pathSpec as "", which is falsy in constructInitialQuery (content = resolved ? \@${resolved}` : part.content) and logs an empty using relative path: `.
  2. Now that directories and files share the same pathSpec, displayLabel, and onDebugMessage format, the if (stats.isDirectory()) and else blocks are near-duplicates.

Suggestion:
Normalize const normalizedRelativePath = relativePath || '.'; and combine both branches:

      const isDirectory = stats.isDirectory();
      const normalizedRelativePath = relativePath || '.';
      resolvedFiles.push({
        part,
        pathSpec: normalizedRelativePath,
        displayLabel: path.isAbsolute(pathName)
          ? normalizedRelativePath
          : pathName,
        absolutePath,
        isDirectory,
      });
      onDebugMessage(
        `Path ${pathName} resolved to ${isDirectory ? 'directory' : 'file'}: ${absolutePath}, using relative path: ${normalizedRelativePath}`,
      );

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I have updated the code to apply relative path normalization and combine the branches.
Thank you for your feedback.

);

const pathSpecsToRead = resolvedFiles.map((rf) => {
const pathSpecsToRead = filesToRead.map((rf) => {

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.

Cleanup ([COMPLEXITY-003]): Remove dead rf.pathSpec.endsWith('**') check

Issue:
rf.pathSpec.endsWith('**') ? path.join(rf.absolutePath, '**') : rf.absolutePath (lines 563–570) was only needed when resolveFilePaths rewrote directories to path.join(relativePath, '**'). Since directories are no longer rewritten to ** and are filtered out via filesToRead above, this branch is now dead code and can be simplified:

  const pathSpecsToRead = filesToRead.map(
    (rf) => rf.absolutePath ?? rf.pathSpec,
  );

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done, I simplified pathSpecsToRead to filesToRead.map((rf) => rf.absolutePath ?? rf.pathSpec) and removed the unused endsWith('**') branch.

Comment on lines +333 to 344
expect(result).toEqual({
processedQuery: [
{
text: `Compare @${relativeFilePath} with files in @Documents/internal/`,
},
{ text: '\n--- Content from referenced files ---' },
{ text: `\nContent from @${relativeFilePath}:\n` },
{ text: fileContent },
{ text: '\n--- End of content ---' },
],
});
expect(mockOnDebugMessage).toHaveBeenCalledWith(
`Path ${dirPath} resolved to directory, using glob: ${resolvedGlob}`,
);
});

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.

Test Coverage ([TEST-002]): Also assert mockAddItem tool-group display in mixed queries

Suggestion:
In this mixed @<file> + @<directory> test, please also assert that mockAddItem was called for the read file (expect(mockAddItem).toHaveBeenCalledWith(expect.objectContaining({ type: 'tool_group' }), 1271)), and consider adding a test case for @${testRootDir} (referencing the workspace root directory via its absolute path).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done, I added the mockAddItem tool_group assertion to the mixed @<file> + @<directory> test and added a unit test verifying that @${testRootDir} normalizes to @..

…andling

- Normalize empty relativePath to '.' in resolveFilePaths when an @ command references the workspace root directory via an absolute path, and consolidate the directory/file resolution branches.
- Simplify pathSpecsToRead in readLocalFiles by removing the dead endsWith('**') check.
- Update handleAtCommand JSDoc and docs/reference/commands.md to reflect that @<directory> resolves the directory path without eagerly reading nested files.
- Add unit test for workspace root directory resolution and assert tool_group display in mixed @<file> + @<directory> queries.
@jvargassanchez-dot
jvargassanchez-dot requested a review from a team as a code owner October 6, 2026 19:55

This branch has not been deployed

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

Labels

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.

2 participants