You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
--path <pattern> is documented as a "pattern" but actually does literal substring containment — --path "*.py" returns 0 results, only --path ".py" works (no glob support, breaks every conventional CLI expectation) #195
Every cdidx command that takes --path <pattern> (search, find, definition, references, callers, callees, symbols, files, map, inspect, outline, unused, hotspots, impact, deps) implements --path as literal substring containment — the user's input is wrapped as '%' || EscapeLikeQuery(input) || '%' and matched with SQL LIKE ... ESCAPE '\\', with % and _ escaped so they cannot act as wildcards.
The CLI help and CLAUDE.md both call the parameter <pattern>. Every conventional CLI tool (rg, find, git ls-files, fd, grep --include, etc.) uses glob patterns for path filters, so a user passing --path "*.py" reasonably expects "match Python files". cdidx silently returns zero results because no path on disk literally contains *.py as a substring.
This is one of the most damaging silent-failure modes for AI consumers: the agent infers from the docs that --path "*.py" should narrow to Python files, sees zero matches, and concludes "the file/symbol doesn't exist" — even though it does.
Six representative inputs, each with the LIKE shape that cdidx generates:
User input
SQL LIKE pattern
Result on the corpus above
--path "*.py"
'%*.py%'
0 matches (no path literally contains *.py)
--path "**/*.py"
'%**/*.py%'
0 matches
--path "src/*.py"
'%src/*.py%'
0 matches
--path "%.py"
'%\%.py%' (escaped)
0 matches (% is escaped before LIKE sees it)
--path "%py%"
'%\%py\%%' (escaped)
0 matches
--path ".py"
'%.py%'
2 matches (works because .py is a substring of every file)
$ /root/.local/bin/cdidx find hello --path "*.py"
No matches found.
Hint: try broadening --path or adding another --path value; --path is required for find.
$ /root/.local/bin/cdidx find hello --path ".py"
a.py:1:5
1: def hello(): pass
b.py:1:5
1: def hello(): pass
The user has to invent a non-glob "what substring is in all my paths" rule. That works for files in subdirectories (--path "src/" to scope to a directory), but breaks immediately for anything fancier — --path "src/*.py" returns 0, you have to express it as --path "src/" --path ".py" (which is OR not AND, so it's also wrong: it returns files matching "src/" OR ".py", not src directory's Python files).
A second, related symptom: there's no way to express a glob like --path "*.test.ts" either — the user has to use --path ".test.ts" and accept mid-word collisions (e.g. manifest.test.tsx matches anything containing .test.ts).
The shape is LIKE '%' || EscapeLikeQuery(input) || '%' ESCAPE '\\'. Because EscapeLikeQuery neutralizes % and _, the user's input cannot contain LIKE wildcards. The leading/trailing % produce substring containment.
Suggested direction
Three reasonable fixes, listed in order of increasing scope. Pick one and document it loudly.
Option A — translate glob to LIKE (smallest user-visible change)
Detect * / ? in the input and translate to LIKE:
* → %
? → _
everything else → EscapeLikeQuery-style escaped
(optional) ** → % (or strip — same semantics in LIKE since % already crosses /)
Then don't wrap with extra % on either side if the user already provided wildcards (otherwise --path "*.py" becomes LIKE '%%.py%' which would match a.py.txt too). Anchored interpretation: if the user provides * anywhere, treat the pattern as anchored on whichever side they didn't put a *.
This is the smallest behavior change consistent with what every other CLI tool does, and matches user/AI expectations.
Option B — true glob with Microsoft.Extensions.FileSystemGlobbing
Use the proper Matcher (already in BCL via NuGet, but adding a dependency violates the cdidx single-dependency rule — out of scope without explicit user approval). Skip unless there's a strong reason.
Option C — keep substring semantics, but rename the flag and document
If "substring containment" is the intended semantics, rename --path <pattern> to --path <substring> and document it explicitly in --help, README, CLAUDE.md, and MCP tool schemas. Then the AI consumer at least knows the rules. (This is the smallest code change but the worst usability for everyone.)
I'd recommend Option A. It satisfies AI/CLI-tool conventions with one helper function and no new dependency.
src/CodeIndex/Database/DbReader.cs:338, 1764-1791 — implement glob→LIKE translator and adjust the wrapping logic. Probably introduce a BuildPathLikePattern(input) helper that returns the final SQL LIKE string + handles wildcard detection.
All command help text — clarify --path semantics.
README (English + Japanese) — document with examples.
DEVELOPER_GUIDE.md and CLAUDE.md design-decisions section — note the chosen semantics.
MCP tool schemas — same semantics, same docs.
tests/CodeIndex.Tests/QueryCommandRunnerTests.cs — add tests for --path "*.py", --path "src/*.py", --path "src/" (no wildcard, anchor implied), --path "**/*.test.ts", and the empty-string case.
Notes for reviewer
I lack a local .NET SDK in this cloud session, so I can't compile/test the fix. The change is mechanical (glob→LIKE conversion) and has clear test cases; CI is required to confirm the SQL behavior across SQLite versions.
Summary
Every cdidx command that takes
--path <pattern>(search,find,definition,references,callers,callees,symbols,files,map,inspect,outline,unused,hotspots,impact,deps) implements--pathas literal substring containment — the user's input is wrapped as'%' || EscapeLikeQuery(input) || '%'and matched with SQLLIKE ... ESCAPE '\\', with%and_escaped so they cannot act as wildcards.The CLI help and CLAUDE.md both call the parameter
<pattern>. Every conventional CLI tool (rg,find,git ls-files,fd,grep --include, etc.) uses glob patterns for path filters, so a user passing--path "*.py"reasonably expects "match Python files". cdidx silently returns zero results because no path on disk literally contains*.pyas a substring.This is one of the most damaging silent-failure modes for AI consumers: the agent infers from the docs that
--path "*.py"should narrow to Python files, sees zero matches, and concludes "the file/symbol doesn't exist" — even though it does.Repro
Six representative inputs, each with the LIKE shape that cdidx generates:
--path "*.py"'%*.py%'*.py)--path "**/*.py"'%**/*.py%'--path "src/*.py"'%src/*.py%'--path "%.py"'%\%.py%'(escaped)%is escaped before LIKE sees it)--path "%py%"'%\%py\%%'(escaped)--path ".py"'%.py%'.pyis a substring of every file)The user has to invent a non-glob "what substring is in all my paths" rule. That works for files in subdirectories (
--path "src/"to scope to a directory), but breaks immediately for anything fancier —--path "src/*.py"returns 0, you have to express it as--path "src/" --path ".py"(which isORnotAND, so it's also wrong: it returns files matching "src/" OR ".py", not src directory's Python files).A second, related symptom: there's no way to express a glob like
--path "*.test.ts"either — the user has to use--path ".test.ts"and accept mid-word collisions (e.g.manifest.test.tsxmatches anything containing.test.ts).Suspected root cause (from reading the source)
Three lines of code combine to produce this:
src/CodeIndex/Database/DbReader.cs:338-341:src/CodeIndex/Database/DbReader.cs:1764-1766:src/CodeIndex/Database/DbReader.cs:1783-1784:The shape is
LIKE '%' || EscapeLikeQuery(input) || '%' ESCAPE '\\'. BecauseEscapeLikeQueryneutralizes%and_, the user's input cannot contain LIKE wildcards. The leading/trailing%produce substring containment.Suggested direction
Three reasonable fixes, listed in order of increasing scope. Pick one and document it loudly.
Option A — translate glob to LIKE (smallest user-visible change)
Detect
*/?in the input and translate to LIKE:*→%?→_EscapeLikeQuery-style escaped**→%(or strip — same semantics in LIKE since%already crosses/)Then don't wrap with extra
%on either side if the user already provided wildcards (otherwise--path "*.py"becomesLIKE '%%.py%'which would matcha.py.txttoo). Anchored interpretation: if the user provides*anywhere, treat the pattern as anchored on whichever side they didn't put a*.This is the smallest behavior change consistent with what every other CLI tool does, and matches user/AI expectations.
Option B — true glob with
Microsoft.Extensions.FileSystemGlobbingUse the proper
Matcher(already in BCL via NuGet, but adding a dependency violates the cdidx single-dependency rule — out of scope without explicit user approval). Skip unless there's a strong reason.Option C — keep substring semantics, but rename the flag and document
If "substring containment" is the intended semantics, rename
--path <pattern>to--path <substring>and document it explicitly in--help, README, CLAUDE.md, and MCP tool schemas. Then the AI consumer at least knows the rules. (This is the smallest code change but the worst usability for everyone.)I'd recommend Option A. It satisfies AI/CLI-tool conventions with one helper function and no new dependency.
Sibling observations (related, not blocking)
find <q> --path ""(empty string) is silently accepted and matches everything (substring''is contained in every path). Should reject empty path patterns or document the wildcard semantics. Same family as CLI arg parser:--limit/--snippet-linesvalidation errors are non-fatal — "Error: ..." prints but command continues with default, exit=0 (and--db=value/ unknown flags have similar silent-fail shapes) #184's silent-bad-input problem.--exclude-testsLIKE patterns documented in--exclude-testsfalse positives:%test.%/%tests.%LIKE patterns match mid-word, silently droppinglatest.py,fastest.py,contest.py,request.py,ingest.rs,digest.goetc. from non-test results #188 (%test.%/%tests.%) work exactly because%is escaped — they're literal substring matches ontest./tests.. That's whylatest.py/request.py/ingest.rsetc. are dropped (they containtest.as a substring).--exclude-testsfalse positives:%test.%/%tests.%LIKE patterns match mid-word, silently droppinglatest.py,fastest.py,contest.py,request.py,ingest.rs,digest.goetc. from non-test results #188 is one specific consequence of this issue's core "substring containment masquerading as pattern matching" problem; fixing this issue fixes--exclude-testsfalse positives:%test.%/%tests.%LIKE patterns match mid-word, silently droppinglatest.py,fastest.py,contest.py,request.py,ingest.rs,digest.goetc. from non-test results #188 too.Scope
src/CodeIndex/Database/DbReader.cs:338, 1764-1791— implement glob→LIKE translator and adjust the wrapping logic. Probably introduce aBuildPathLikePattern(input)helper that returns the final SQL LIKE string + handles wildcard detection.--pathsemantics.tests/CodeIndex.Tests/QueryCommandRunnerTests.cs— add tests for--path "*.py",--path "src/*.py",--path "src/"(no wildcard, anchor implied),--path "**/*.test.ts", and the empty-string case.Notes for reviewer
I lack a local .NET SDK in this cloud session, so I can't compile/test the fix. The change is mechanical (glob→LIKE conversion) and has clear test cases; CI is required to confirm the SQL behavior across SQLite versions.
Related
--limit/--snippet-linesvalidation errors are non-fatal — "Error: ..." prints but command continues with default, exit=0 (and--db=value/ unknown flags have similar silent-fail shapes) #184 — silent acceptance of nonsensical CLI input is the broader pattern.--exclude-testsfalse positives:%test.%/%tests.%LIKE patterns match mid-word, silently droppinglatest.py,fastest.py,contest.py,request.py,ingest.rs,digest.goetc. from non-test results #188 —%test.%mid-word matches droppinglatest.py/request.pyetc. is one symptom of substring-as-pattern semantics.Environment
install.sh).CLOUD_BOOTSTRAP_PROMPT.md.