[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

Open 💬 0 comments Opened Jul 14, 2026 by proovend

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:

  1. --no-ignore — no .gitignore / .ignore / .rgignore can prune the walk. Tested directly: with an .rgignore (and separately an .ignore) listing the heavy directories, raw rg obeys it and finishes in ~0.1s, while Glob still times out. There is no repo-side way to scope the walk.
  2. --hidden — dot-directories are walked too (e.g. an in-tree .worktrees/ forest of git worktrees).
  3. --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-ignore
  • CLAUDE_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 prunes forest/)
  • Glob **/target.txt at repo root → 20s timeout once forest/ is large enough, because the hardcoded --no-ignore --hidden --sort=modified walk must visit every inode, single-threaded

Asks

  1. Default to respecting ignore files, like every other tool built on this stack (rg and fd are instant on this exact tree precisely because of that default) — or at minimum drop --sort=modified from 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).
  2. 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=0 to respect ignore files" can.
  3. Document CLAUDE_CODE_GLOB_NO_IGNORE and CLAUDE_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.

View original on GitHub ↗