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 #28416

Description

What happened?

GlobTool.execute() carefully resolves symlinks when building internal path sets (lines 208–242), but the final output handed to the LLM (line 273) uses entry.fullpath() — the raw, symlink-unresolved path from the glob library.

This inconsistency means the path strings returned to the model differ from the canonical paths that read_file, edit, and write_file expect after resolving their inputs — causing "file not found" or "path not in workspace" failures on follow-up tool calls.

Root cause — two different path forms used in the same function:

Internal resolution (correct) — lines 208–222:

let realTargetDir = this.config.getTargetDir();
try { realTargetDir = resolveToRealPath(realTargetDir); } catch { }

const relativePaths = allEntries.map((p) => {
  let realFullPath = p.fullpath();
  try { realFullPath = resolveToRealPath(realFullPath); } catch { }
  return path.relative(realTargetDir, realFullPath); // real ↔ real ✓
});

Final output handed to LLM (inconsistent) — lines 272–275:

const sortedAbsolutePaths = sortedEntries.map((entry) =>
  entry.fullpath()  // ← raw symlink path, NOT resolved
);

The glob library v12 documents this explicitly: fullpath() does "only string path resolution — does not make an extra system call to get the realpath". So when cwd is a symlink, entry.fullpath() returns the unresolved path.

This affects every macOS user because macOS resolves /tmp/private/tmp. Any project opened from /tmp/myproject will have getTargetDir() = /tmp/myproject but tools that call resolveToRealPath internally see /private/tmp/myproject — the paths from GlobTool never match.

What did you expect to happen?

The file paths returned to the LLM should be the canonical, symlink-resolved absolute paths — consistent with what read_file, edit, and write_file will accept after their own path resolution.

Suggested fix:

Build a raw → real path map during the existing relativePaths loop (no extra fs calls needed) and use it when constructing the final output:

const rawToReal = new Map<string, string>();
allEntries.forEach((p) => {
  let real = p.fullpath();
  try { real = resolveToRealPath(real); } catch { }
  rawToReal.set(p.fullpath(), real);
});

// In the output section, replace entry.fullpath() with:
const sortedAbsolutePaths = sortedEntries.map((entry) =>
  rawToReal.get(entry.fullpath()) ?? entry.fullpath()
);

I am happy to open a PR with this fix and a unit test that creates a symlink workspace and asserts all returned paths are canonical — 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)
glob dependency version: 12.0.0 (per packages/core/package.json)

Login information

Not auth-dependent — reproducible with any login method.

Anything else we need to know?

How to reproduce:

  1. Create a symlink workspace:
   mkdir -p /tmp/realproject/src
   ln -s /tmp/realproject /tmp/symproject
   echo "export const x = 1;" > /tmp/realproject/src/index.ts
  1. Launch Gemini CLI with the symlinked directory as the target.
  2. Ask: "Find all TypeScript files"
  3. GlobTool runs with cwd = /tmp/symproject.
  4. Result returned to model: /tmp/symproject/src/index.ts (raw path).
  5. Model calls read_file({ absolute_path: "/tmp/symproject/src/index.ts" }).
  6. read_file resolves to /tmp/realproject/src/index.ts internally → path mismatch → error.

On macOS this is always active — no symlink setup needed. /tmp is a symlink to /private/tmp on every macOS installation.

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

  • Correct internal resolution: lines 208–222
  • Inconsistent final output: lines 272–275
  • glob v12 fullpath() behaviour: confirmed via official npm docs

This is distinct from issue #1121 (symlink security bypass) — that is about path validation escaping the sandbox. This bug is about path format inconsistency between GlobTool output and what other tools accept as input.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/agentIssues related to Core Agent, Tools, Memory, Sub-Agents, Hooks, Agent Qualitystatus/need-triageIssues that need to be triaged by the triage automation.

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions