Measured 2026-10-06 (cyc20261006-192020) on this checkout. glob filters every
match through _is_hidden_or_ignored and then reports the filtered result as if it
were the answer about the tree — and when the filter takes everything, it says the
pattern matched nothing:
# emrg/gui, a directory that really holds node_modules:
$ glob(pattern='node_modules/**/package.json', workdir='<repo>/emrg/gui')
No files matched pattern 'node_modules/**/package.json' in <repo>/emrg/gui
matched 352 path(s) · kept 0 · skipped 352
$ glob(pattern='.git/config', workdir='<repo>')
No files matched pattern '.git/config' in <repo>
matched 1 path(s) · kept 0 · skipped 1 (the file is there)
$ glob(pattern='**', workdir='<repo>/emrg/gui')
Found 293 matches for '**' in <repo>/emrg/gui:
matched 18014 path(s) · kept 293 · skipped 17721
(matched/kept counted with the tool's own predicate, GlobTool._is_hidden_or_ignored.)
It is the class this repository keeps fixing: a reading that was not made is not a verdict
0 is the code that means measured and clean, and "No files matched" is the sentence
that means the tree has none. Here no search of those paths happened at all — the
policy dropped them — so the tool states a fact about the tree that it did not measure.
It is the same defect as grep's Found N matches counting context lines (issue
#1805), one step earlier: an answer an agent uses for "is this dependency installed?",
"is there a config file here?", "how many Python files does this project have?" —
answered 0 by the skip rather than by the tree.
The non-zero case is the same sentence with a smaller error: Found 293 matches is the
number left after the skip, presented as the number the tree holds. 18014 paths matched
that pattern.
The sibling tool already handles both halves
grep skips the same directories and does two things glob does not:
- its description declares it — "cross-platform pattern search with automatic
binary/hidden file skipping" — while glob's description says only "Results are capped
at 500 matches, sorted by path", so the caller is not told a filter exists;
- its count line names its subject —
Found N matches for 'X' in <root> (searched M files): — so a reader can tell a filtered count from the count.
The remedy is real, and measured
The skip compares each path relative to the root it was given, so pointing workdir
at the directory that holds them reads them:
$ glob(pattern='config', workdir='<repo>/.git') -> Found 1 matches for 'config' ...
A refusal that names a remedy nobody ran is worse than none, so this one is pinned by a
test that fails when the comparison stops being relative.
Acceptance
- a pattern whose every match was skipped is not reported as
No files matched
without the number of paths the skip dropped, and the sentence says the skip — not the
tree — is what answered;
- the
Found N line carries the same number, in the shape grep uses for its own
subject, so a filtered count is not read as the count;
- the remedy the refusal names is the measured one (
workdir at the directory that holds
them), and a test fails if the skip stops being relative to the workdir;
- the tool description declares the skipping, as
grep's does, and names workdir as
the way to read inside a skipped directory;
- each of those legs has a test that fails when its clause is removed, and a control that
fails when the clause is printed for a pattern that skipped nothing.
Measured 2026-10-06 (
cyc20261006-192020) on this checkout.globfilters everymatch through
_is_hidden_or_ignoredand then reports the filtered result as if itwere the answer about the tree — and when the filter takes everything, it says the
pattern matched nothing:
(
matched/keptcounted with the tool's own predicate,GlobTool._is_hidden_or_ignored.)It is the class this repository keeps fixing: a reading that was not made is not a verdict
0is the code that means measured and clean, and "No files matched" is the sentencethat means the tree has none. Here no search of those paths happened at all — the
policy dropped them — so the tool states a fact about the tree that it did not measure.
It is the same defect as
grep'sFound N matchescounting context lines (issue#1805), one step earlier: an answer an agent uses for "is this dependency installed?",
"is there a config file here?", "how many Python files does this project have?" —
answered
0by the skip rather than by the tree.The non-zero case is the same sentence with a smaller error:
Found 293 matchesis thenumber left after the skip, presented as the number the tree holds. 18014 paths matched
that pattern.
The sibling tool already handles both halves
grepskips the same directories and does two thingsglobdoes not:binary/hidden file skipping" — while
glob's description says only "Results are cappedat 500 matches, sorted by path", so the caller is not told a filter exists;
Found N matches for 'X' in <root> (searched M files):— so a reader can tell a filtered count from the count.The remedy is real, and measured
The skip compares each path relative to the root it was given, so pointing
workdirat the directory that holds them reads them:
A refusal that names a remedy nobody ran is worse than none, so this one is pinned by a
test that fails when the comparison stops being relative.
Acceptance
No files matchedwithout the number of paths the skip dropped, and the sentence says the skip — not the
tree — is what answered;
Found Nline carries the same number, in the shapegrepuses for its ownsubject, so a filtered count is not read as the count;
workdirat the directory that holdsthem), and a test fails if the skip stops being relative to the workdir;
grep's does, and namesworkdirasthe way to read inside a skipped directory;
fails when the clause is printed for a pattern that skipped nothing.