[BUG] Glob tool hardcodes `--no-ignore --hidden --sort=modified`: un-prunable single-threaded walk guarantees 20s timeouts on large monorepos; escape hatches CLAUDE_CODE_GLOB_NO_IGNORE / CLAUDE_CODE_GLOB_HIDDEN work but are undocumented and unnamed in the error
Summary
The Glob tool spawns ripgrep as (captured live via ps during a Glob call; CLI 2.1.209, macOS):
rg --no-config --files --glob <pattern> --sort=modified --no-ignore --hidden <path>
Three hardcoded flags compound:
--no-ignore— no.gitignore/.ignore/.rgignorecan prune the walk. Tested directly: with an.rgignore(and separately an.ignore) listing the heavy directories, rawrgobeys it and finishes in ~0.1s, while Glob still times out. There is no repo-side way to scope the walk.--hidden— dot-directories are walked too (e.g. an in-tree.worktrees/forest of git worktrees).--sort=modified— per the ripgrep docs, sorting results "currently always forces ripgrep to abandon parallelism", so the walk is also single-threaded.
Net effect: every Glob call is an O(every inode under path), single-threaded filesystem walk racing a fixed 20-second timeout, and neither the user nor the repo can prune it.
Pattern prefixes do not help: the glob is a post-filter on the walk, not a walk root. apps/**/tsconfig.json invoked from repo root walks the entire tree and times out exactly like **/* would.
Measurements
Repo shape: pnpm monorepo with in-tree git worktrees under a gitignored .worktrees/ directory — ~7.9M gitignored inodes (~97% of the walk; the worktree forest alone is 7,859,619 files); the non-ignored tree is ~5,800 files.
| Invocation | Outcome |
|---|---|
| Glob tool, **/go.mod, path = repo root | timeout error at the fixed 20s budget (reproduced 5/5) |
| Glob tool, apps/**/tsconfig.json, path = repo root (prefix-scoped) | timeout error at 20s |
| The exact captured rg invocation, replayed to completion outside the harness | 76.15s wall at ~52% CPU (single-threaded), 14 matches |
| Same invocation minus --no-ignore (what CLAUDE_CODE_GLOB_NO_IGNORE=0 produces) | 0.11–0.36s across runs (~200x faster) |
| Same invocation minus --hidden (what CLAUDE_CODE_GLOB_HIDDEN=0 produces) | ~0.16s |
| Stock rg --files -g '**/go.mod' on the same tree (ignore-respecting defaults) | 0.03s |
7-day transcript audit on this machine: 74 of 219 Glob calls failed (33.8%); for orchestrated (workflow) agents, 60 of 121 (49.6%). Every sampled failure is this 20s walk timeout.
The escape hatches exist but are invisible
The binary's argument construction reads two env vars, both defaulting to true (truth set {1, true, yes, on}; any other value drops the corresponding flag):
CLAUDE_CODE_GLOB_NO_IGNORE— controls--no-ignoreCLAUDE_CODE_GLOB_HIDDEN— controls--hidden
Setting CLAUDE_CODE_GLOB_NO_IGNORE=0 takes repo-root Glob on this tree from guaranteed timeout to sub-second (~200x measured on the raw walk). But:
- neither variable appears in the documentation;
- the timeout error suggests trying a more specific path or pattern — advice that cannot work here, because pattern prefixes do not prune the walk root, and a more specific path is usually exactly what the agent does not know yet (that is why it is globbing);
- so in practice the model retries a variation of the same doomed call until it gives up on the tool.
Repro sketch
Any repo plus a gitignored directory holding a few million inodes:
git init glob-repro && cd glob-repro
echo 'forest/' > .gitignore
# populate forest/ with several million files (e.g. many copies of a node_modules tree)
# add a handful of tracked files matching some pattern, e.g. target.txt
rg --files -g '**/target.txt'→ milliseconds (ignore-respecting default prunesforest/)- Glob
**/target.txtat repo root → 20s timeout onceforest/is large enough, because the hardcoded--no-ignore --hidden --sort=modifiedwalk must visit every inode, single-threaded
Asks
- Default to respecting ignore files, like every other tool built on this stack (
rgandfdare instant on this exact tree precisely because of that default) — or at minimum drop--sort=modifiedfrom the walk and sort the matched paths post-hoc, restoring ripgrep's parallelism (sorting N result paths is cheap; forcing a single-threaded walk of the whole tree to get them pre-sorted is not). - Name the env knobs in the timeout error. "Try a more specific path or pattern" cannot fix an un-prunable walk; "set
CLAUDE_CODE_GLOB_NO_IGNORE=0to respect ignore files" can. - Document
CLAUDE_CODE_GLOB_NO_IGNOREandCLAUDE_CODE_GLOB_HIDDEN.
The tradeoff of an ignore-respecting default — gitignored files (build outputs, .env*) become invisible to Glob — is the same tradeoff every ripgrep/fd user already lives with as the default, and both knobs already exist for anyone who wants the exhaustive walk back.