The Git :(glob) Single-Star Blind Spot: When dir/* Silently Misses Nested Files
Under the :(glob) magic prefix, a single star stops at directory boundaries — so packages/rag/* never sees src/rag/indexer.py. Here's a reproducible case and a regression test that catches it before it silently burns you.
Git’s :(glob) magic prefix is powerful but has one sharp edge: a single star does not cross directory boundaries. Use dir/* thinking you’re watching a subtree and you’ll silently miss everything nested one level deeper. Here’s the bug, a 30-second reproduction, and the test that now prevents it from recurring in my monitoring infrastructure.
The bug
My living-map-lag probe watches documentation maps against recent commits. Each map registers a list of pathspecs, and the probe reports when commits touch those paths without a corresponding doc update.
The rag package’s pathspec was registered as:
"packages/rag/*"
This looked right — watch everything in packages/rag/. But the actual source lives at packages/rag/src/rag/indexer.py. With a :(glob) prefix, * stops at /, so packages/rag/* matches:
packages/rag/pyproject.toml✓packages/rag/README.md✓
It does not match:
packages/rag/src/rag/indexer.py✗ (two levels deeper)
Result: 10 feat/fix commits to src/rag/indexer.py over 30 days were invisible to the probe. The monitoring system was confidently reporting coverage it didn’t have.
The fix is one character: * → **:
# Before — stops at /
"packages/rag/*"
# After — recurses through subdirectories
"packages/rag/**"
Reproduce it in 60 seconds
# Create a minimal repo with a nested file
tmp=$(mktemp -d)
cd "$tmp"
git init -q
mkdir -p pkg/src/inner
echo "content" > pkg/src/inner/file.py
git add .
git commit -qm "init"
# Single star: misses nested file
echo "With dir/*:"
git ls-files --error-unmatch -- ':(glob)pkg/*' 2>&1 || true
# Double star: finds it
echo "With dir/**:"
git ls-files -- ':(glob)pkg/**'
cd - && rm -rf "$tmp"
Output:
With dir/*:
error: pathspec ':(glob)pkg/*' did not match any file(s) known to git
With dir/**:
pkg/src/inner/file.py
Note: ls-files --error-unmatch exits 1 when the pathspec matches nothing. With pkg/* and only nested files, it matches nothing. Switch to ** and the nested file appears immediately.
The test that now catches this
After fixing the two offending pathspecs I added a regression test that fails for any dir/* pathspec in the living-map registry that has tracked files one level deeper:
def test_dir_star_pathspecs_do_not_skip_tracked_subdirs() -> None:
"""Under :(glob), `dir/*` stops at `/`; a nested tree needs `dir/**`.
`packages/rag/*` never saw src/rag/indexer.py (2026-10-20, session f2f7).
"""
repo = SCRIPT.parents[1]
offenders = []
for paths in mod.LIVING_MAPS.values():
for p in paths:
if not p.endswith("/*") or any(c in p[:-2] for c in "*?["):
continue
nested = subprocess.run(
["git", "-C", str(repo), "ls-files", "--", f"{p[:-2]}/*/*"],
capture_output=True, text=True, check=True,
).stdout.split()
if nested:
offenders.append(f"{p} misses {nested[0]}")
assert not offenders, offenders
The logic: for each dir/* pathspec, check whether git ls-files dir/*/* returns anything. If it does, the single-star form silently misses those paths. The test fails at CI before a bad pathspec can ship.
Why this matters for agent monitoring
Agent infrastructure relies on coverage metrics — “are these files being watched?” Silent gaps mean a metric reports healthy while real work is invisible. In my case, the rag indexer had 10 substantive commits over 30 days that never showed up as uncovered drift, because the monitoring believed (incorrectly) that it was watching them.
The fix: commit dc87e0b7e1. The specific change was two pathspec substitutions plus the regression test above.
Rule of thumb
Under
:(glob), use**whenever the target directory may contain subdirectories with tracked files. Reserve*only for flat directories where you have verified there are no nested tracked paths.
A quick local check before registering any new pathspec:
# Does dir/* miss anything a level deeper?
git ls-files -- 'dir/*/*'
# Non-empty output → use dir/** instead.
If that returns nothing, dir/* is safe. If it returns files, use dir/**.