Skip to content

[Bug] GlobTool returns raw symlink paths in results but uses resolved real paths internally — causes downstream read_file/edit failures on macOS and symlinked workspaces #28415

Description

What happened?

GlobTool.validateToolParamValues checks exactly one directory (config.getTargetDir()) but GlobToolInvocation.execute() searches all directories returned by workspaceContext.getDirectories().

When those sets differ (multi-root workspaces via includeDirectories), the extra workspace directories are never validated. If any of them is stale, deleted, or inaccessible, glob silently returns zero entries for that root — no error is surfaced to the model or the user. The result is either a silent partial match or a false "No files found" response.

Root cause — two separate code paths with mismatched scope:

validateToolParamValues (lines 333–374 of packages/core/src/tools/glob.ts):

searchDirAbsolute = resolveToRealPath(
  path.resolve(this.config.getTargetDir(), params.dir_path || '.'),
);

When dir_path is absent this always resolves to getTargetDir() — one directory. The fs.existsSync / fs.statSync checks only apply to that one directory.

execute() (lines 176–179):

} else {
  // Search across all workspace directories
  searchDirectories = workspaceDirectories;
}

workspaceDirectories can contain many roots. None of the extra roots were validated. Glob failures on them are swallowed silently.

Secondary — dead code on line 353:

const targetDir = searchDirAbsolute || this.config.getTargetDir();

searchDirAbsolute is always assigned before this line (or the function returned early), so the fallback branch is unreachable. This indicates the validation and execution paths have drifted apart.

What did you expect to happen?

validateToolParamValues should validate the same set of directories that execute() will actually search.

When dir_path is absent, it should iterate workspaceContext.getDirectories() and validate each one — mirroring exactly what execute() does. If any workspace directory is inaccessible, an error should be returned before execution begins, rather than silently producing partial or empty results.

Suggested fix:

protected override validateToolParamValues(params: GlobToolParams): string | null {
  const dirsToValidate = params.dir_path
    ? [path.resolve(this.config.getTargetDir(), params.dir_path)]
    : this.config.getWorkspaceContext().getDirectories();

  for (const dir of dirsToValidate) {
    let resolved: string;
    try {
      resolved = resolveToRealPath(dir);
    } catch (err) {
      return err instanceof Error ? err.message : String(err);
    }
    const accessError = this.config.validatePathAccess(resolved, 'read');
    if (accessError) return accessError;
    if (!fs.existsSync(resolved)) return `Search path does not exist: ${resolved}`;
    if (!fs.statSync(resolved).isDirectory()) return `Search path is not a directory: ${resolved}`;
  }

  if (!params.pattern || params.pattern.trim() === '') {
    return "The 'pattern' parameter cannot be empty.";
  }
  return null;
}

I am happy to open a PR with this fix and a unit test covering multi-root workspace validation once the approach is confirmed — please assign me.

Client information

Client Information

Run gemini to enter the interactive CLI, then run the /about command.

> /about
Reproduced from source zip (main branch, nightly 0.51.0-nightly.20260625.g3fbf93e26)
Platform: Linux (Ubuntu 24.04)
Node.js: v20.x (required minimum per gemini-cli package.json)

Platform: Linux / macOS (any OS with multi-root includeDirectories workspace)

Login information

Not auth-dependent — reproducible with any login method.

Anything else we need to know?

How to reproduce:

  1. Configure two includeDirectories roots in ~/.gemini/settings.json:
   { "includeDirectories": ["/valid/project", "/tmp/stale-dir"] }
  1. Ensure /tmp/stale-dir does not exist on disk.
  2. Launch Gemini CLI from /valid/project.
  3. Ask the model to find files: "find all *.ts files"
  4. GlobTool is invoked without dir_pathvalidateToolParamValues passes (only checks /valid/project).
  5. execute() attempts to iterate both dirs → silent empty result for /tmp/stale-dir.

Result: Files from /valid/project may be missing from results with no error shown.

Exact lines in packages/core/src/tools/glob.ts:

  • Validation scope (too narrow): lines 333–374
  • Execution scope (all workspace dirs): lines 139–179
  • Dead code fallback: line 353

This is distinct from existing issues #1118 (feature request for multi-dir support) and #12565 (symlink traversal) — those concern different behaviour.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/coreIssues related to User Interface, OS Support, Core Functionalityeffort/small1 day or less: trivial logic, UI adjustments, docskind/bugpriority/p2Important but can be addressed in a future release.status/bot-triaged

    Type

    Fields

    No fields configured for Bug.

    Projects

    Status
    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions