[BUG] Glob/Grep slash globs anchor to launch cwd instead of `path`
Preflight Checklist
- [x] I searched existing issues. Related reports #43178, #47042, and #37046 were closed/locked without a fix; this report adds a reduced repro, root-cause analysis, cross-platform confirmation, and a regression matrix.
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
TL;DR: Glob/Grep pass glob patterns to embedded ripgrep as -g overrides anchored at the launch cwd, not the path argument. Any slash-containing pattern with path ≠ cwd returns a silent empty — indistinguishable from verified absence. Fix: build the override matcher from path (OverrideBuilder::new(path)).
Glob/Grep glob patterns containing / behave as if anchored to the process working directory (the directory Claude Code was launched from) instead of the path argument, producing silent empty results whenever path ≠ cwd.
(Note: the issue form's Platform/OS fields are single-select; this is reproduced on Windows native and WSL2 Linux.)
Concretely: Glob(pattern: "sub/*.txt", path: "/tmp/repro2") returns "No files found" even though /tmp/repro2/sub/b.txt exists — including for exact literal paths (sub/b.txt) and the docstring's own example shape (src/**/*.ts). Grep's glob parameter fails identically. There is no error or warning: the empty result is byte-identical to verified absence, so an agent's next action is built on a silent false negative — it concludes the file doesn't exist, nothing references the symbol, no test covers the path.
Two properties make this worse than a normal search bug:
- Slashless and
**/-prefixed patterns still work, so agents quietly widen dead patterns until they match. The failure self-conceals instead of surfacing — which is likely why three prior reports died untriaged. - Negated globs fail inverted.
Grep(glob: "!node_modules/**", path: <dir ≠ cwd>)anchors the exclusion at cwd, where it can never match anything underpath— so it silently excludes nothing: false positives and unbounded walks (observed asRipgrep search timed out after 20 secondson large trees the exclusion was meant to prune).
The trigger condition — path ≠ cwd — is the tool's normal operating condition, not an edge case: the harness discourages cd and instructs agents to pass absolute path arguments, and subagents, worktrees, and scratchpad directories are assigned locations outside the launch directory. The path parameter exists precisely so searches don't depend on cwd; it is exactly when using it that slash globs return empty.
What Should Happen?
Patterns should be interpreted relative to the tool's path argument — or the tool should warn in the result that glob matching is anchored elsewhere. (The docstring defines no anchoring semantics either way; a silent empty is the one behavior that can't be correct.)
Error Messages/Logs
# process cwd = $HOME ; file /tmp/repro2/sub/b.txt exists
Glob(pattern: "sub/*.txt", path: "/tmp/repro2") -> No files found
Glob(pattern: "sub/b.txt", path: "/tmp/repro2") -> No files found
Grep(pattern: "probe", glob: "sub/*.txt", path: "/tmp/repro2") -> No files found
# no error, no warning — silent empty
Steps to Reproduce
- Create a fixture OUTSIDE the directory Claude Code will be launched from:
``bash``
mkdir -p /tmp/repro2/sub
echo probe > /tmp/repro2/a.txt
echo probe > /tmp/repro2/sub/b.txt
- Start Claude Code in any other directory (launch cwd ≠
/tmp/repro2).
- Run these tool calls:
| # | Tool call | Actual | Expected (relative to path) |
| ---- | ----------------------------------------------------- | ------------------- | ----------------------------- |
| 1 | Glob sub/*.txt path=/tmp/repro2 | No files found | sub/b.txt |
| 2 | Glob sub/b.txt path=/tmp/repro2 (exact literal) | No files found | sub/b.txt |
| 3 | Glob *.txt path=/tmp/repro2 | a.txt AND sub/b.txt | a.txt (root level only) |
| 4 | Glob **/*.txt path=/tmp/repro2 | both (control hit) | both |
| 5 | Glob **/sub/*.txt path=/tmp/repro2 | sub/b.txt | sub/b.txt |
| 6 | Grep pattern=probe glob=sub/*.txt path=/tmp/repro2 | No matches | sub/b.txt |
| 7 | Grep pattern=probe path=/tmp/repro2 (control) | both files | both files |
| 8 | Grep pattern=probe glob=!sub/** path=/tmp/repro2 | both files | a.txt only (sub/ excluded) |
Row 3 is a control, not a claimed defect: slashless patterns matching at any depth is documented gitignore-glob behavior. The defect is rows 1/2/6/8, where the same gitignore semantics can never match under the tool's advertised usage.
- Repeat rows 1, 2, 6 with launch cwd ==
/tmp/repro2(default path): all three now MATCH. The cwd flip is the entire difference. (It is the process cwd that matters; if the session's tracked cwd drifts from the launch directory, results follow the launch directory.)
Same behavior with ripgrep directly, confirming the mechanism:
rg --files -g "sub/*.txt" /tmp/repro2 # cwd elsewhere -> exit 1, no output
(cd /tmp/repro2 && rg --files -g "sub/*.txt" .) # -> ./sub/b.txt
rg --files -g "*.txt" /tmp/repro2 # basename glob -> matches at all depths
rg --files -g "**/sub/*.txt" /tmp/repro2 # ** prefix -> matches
Claude Model
Not sure / Multiple models
Is this a regression?
No, this never worked. The anchoring behavior is present in the embedded rg 14.1.1 builds (Claude Code 2.1.78–2.1.201) through standalone rg 15.1.0, and the silent-empty signature appears on every Claude Code version observed in a transcript corpus from 2.1.9 through 2.1.200 (37 distinct versions; see Additional Information).
Last Working Version
_No response_
Claude Code Version
2.1.201 (Claude Code). Reproduced in four environments, full matrix each time:
- 2.1.78, interactive, Linux (WSL2 Ubuntu 24.04) — session binary verified via
/proc/<pid>/exe. - 2.1.201, headless
claude -p, Linux (WSL2) — with--allowedTools "Glob,Grep". - 2.1.200, native Windows 11, interactive — binary byte-verified against
versions/2.1.200. - 2.1.200, native Windows 11 — independent re-run by a different session against a fresh fixture; identical results.
Platform
Anthropic API
Operating System
Windows
Terminal/Shell
Windows Terminal
Additional Information
Root cause (ripgrep source, BurntSushi/ripgrep @ 4519153): the tools pass patterns to embedded ripgrep as -g override globs. crates/core/flags/hiargs.rs:1217 anchors the override matcher at the process cwd (OverrideBuilder::new(&state.cwd)), not the searched path. Per crates/ignore/src/overrides.rs:91–96, the root prefix is stripped from candidates before matching, and the strip is prefix-only (crates/ignore/src/gitignore.rs:286–302) — so candidates under a search path outside cwd retain a path that a relative slash glob can never consume. This is longstanding upstream behavior, not a ripgrep regression; the fix belongs in how Claude Code invokes the embedded search.
Embedded ripgrep verified directly. The session shell shim execs the Claude Code binary with ARGV0=rg (multiplexed embedded ripgrep), so the tool-call matrix and the direct rg test exercise the same binary. ARGV0=rg <claude-binary> --version reports ripgrep 14.1.1 (rev 63bb0ca0da) for both the 2.1.200 and 2.1.201 binaries, on Windows and Linux, and the cwd-flip reproduces against them with no Claude Code session:
ARGV0=rg <claude-binary> --files -g "sub/*.txt" /tmp/repro2 # cwd elsewhere -> exit 1
(cd /tmp/repro2 && ARGV0=rg <claude-binary> --files -g "sub/*.txt" .) # -> ./sub/b.txt
Combined with standalone rg 15.1.0, the anchoring spans every rg build tested (14.1.1 → 15.1.0). The mechanism has no platform conditional; the platform:windows label on #43178 reflects where it was observed, not the bug.
Real-world impact. The minimal repro above is sufficient to reproduce the bug. The corpus study below is included only to show real-world impact and frequency. Mining one user's complete local transcript corpus (~/.claude/projects: 3,151 transcript files, 25,109 Glob/Grep calls): after canonicalizing path aliases and excluding errors/interrupts, relative slash globs with path ≠ cwd produced 349 silent empties and zero verified matches across 37 projects, 70 sessions, and 37 versions (2.1.9–2.1.200); 35% were in subagent transcripts. Of those 349: 68% forced the agent to redo the search in Bash (rg/ls/find — the exact behavior these tools exist to replace), 18% were retried with widened patterns, 9% were followed by assistant text asserting absence, and 11% were literal-path existence checks. Two false findings are git-provable: an agent reported a component file as missing ("Critical finding — imported but the Glob returned no file") hours after git recorded the file's addition at a path its pattern matched exactly; another concluded "No seed file!" (twice) for a src/db/seed.ts that had been in git for three days. Nineteen same-scope widen-and-retry pairs show the self-concealment directly. Extraction scripts and the anonymized call ledger are available on request.
Prior reports (states re-verified via the GitHub API on 2026-07-04):
- #43178 — "Glob tool returns no results when pattern starts with a literal subdirectory name". Labels: bug / has repro / platform:windows / area:tools / stale. Closed not_planned 2026-05-14 by github-actions[bot] ("inactive for too long"), locked; 4 comments = 3 bot + 1 user; no maintainer comment.
- #47042 — "Glob tool returns empty for path-prefixed patterns when path parameter is set". Closed as duplicate, locked.
- #37046 — "Glob tool silently returns empty results with relative prefix patterns + path parameter". Closed as duplicate, locked.
Repro note for 2.1.201: in some configurations Glob/Grep are not in the default toolset (headless claude -p reports "No such tool available: Glob"); they mount with --allowedTools "Glob,Grep". On 2.1.200 both are in the default core and the repro runs as written.
Suggested fixes, in preference order:
- Construct the override matcher from the resolved absolute
path(OverrideBuilder::new(path)). Validated: identical globs succeed when the anchor equals the search root, including against the embedded 14.1.1 builds. (The equivalent "run the search with cwd =path" variant requires resolving a relativepathto absolute first, and is race-prone if the embedded search runs in-process alongside concurrent tool calls — prefer the matcher-root form.) - Pattern-rewrite shim: when the pattern contains
/andpath≠ cwd, join the pattern topathbefore handing it to ripgrep. Validated: absolute patterns match today. Caveat: negated globs must be rewritten as!<path>/<glob>— path inserted after the!— or the shim corrupts exclusions. - At minimum, detect the anchoring mismatch and warn in the tool result instead of returning a silent empty set.
The probe matrix above (including row 8, the negated-glob row) doubles as a regression gate.