Environment
- gortex v0.60.0+dd3bb30, Windows 11 Pro (10.0.26200)
- 1 tracked repo;
daemon status shows search sqlite-fts5 docs=72707
(so the text index exists and is populated)
Repro (100% reproducible here)
$ gortex call search_text --arg query="setupOutboxDispatcher" --arg limit=3
{"count": 0, "matches": [], "query": "setupOutboxDispatcher",
"scope_note": "0 results within the active scope (repo:domain-manage) — widen with repo:\"*\" or pass an explicit repo:/project:"}
$ gortex call search_text --arg query="setupOutboxDispatcher" --arg repo="*" --arg limit=3
{"count": 0, ...}
$ gortex call search_text --arg query="CREATE TABLE" --arg repo="*" --arg limit=5
{"count": 0, ...}
Control facts:
search_symbols finds setupOutboxDispatcher instantly (it exists at
services/domain-core/cmd/dispatcher/main.go:388).
git grep -c "CREATE TABLE" → 39 files under db/migrations alone.
- FTS index reports 72,707 docs.
So this is not "term not indexed" — every query returns zero, and the
scope_note's suggested escape hatch (repo:"*") also returns zero, which
makes the note actively misleading.
Confirmed repro — broken even in SOLO mode on a fresh daemon
Isolated home, fresh store, ONE tracked repo of 3 files containing the
literal UNIQUE_LITERAL_ALPHA_7391 in an indexed .go file (verified via
search_symbols finding its enclosing function):
SOLO: count: 0 scope_note: 0 results within the active scope (repo:repo-a) ...
MULTI (2 repos): count: 0
AFTER UNTRACK: count: 0
repo="*": count: 0
So this is not an artifact of our production daemon's history — the CLI
search_text path returns zero in the most basic configuration possible.
Note the scope resolves to repo:repo-a while solo-mode matches carry
unprefixed paths (see #309): if the scope filter compares
match-path prefixes against RepoAllow, every solo match fails the test.
repo:"*" also returning 0 suggests the wildcard from the scope_note hint is
taken as a literal repo name rather than a widen.
Why this matters more than a broken verb
Agents are told zero results might mean "does not exist". A search verb that
returns zero for everything — while suggesting a widen that doesn't work —
manufactures false "X does not exist" conclusions. (We caught it only by
running a known-positive control; that control discipline is not something
every agent does.)
Diagnostic clue (from reading internal/mcp/tools_search_text.go)
Matches flow through filterTextMatchesByPath(..., repoPrefixes) and then
filterTextMatchesByResolvedScope(matches, resolved). Combined with the
companion dual-identity issue (repo registered as name `` at one layer and
domain-manage at another), the likely failure is:
- match paths are stamped with one identity (e.g. empty prefix, solo-repo
form: services\...),
resolved.RepoAllow carries the other (domain-manage),
- the scope filter drops every match →
count=0,
- and
repo:"*" may not reach this session-bound resolver at all
("session-bound sessions may only name their own workspace").
Proposed fix
- In
filterTextMatchesByResolvedScope, treat a match whose path resolves
inside a repo allowed by RepoAllow as allowed regardless of which ID
form the path carries (normalize both sides to absolute path or to one
canonical prefix before comparing).
- When the post-filter count is zero but the pre-filter count was non-zero,
say so in scope_note (e.g. "N matches were excluded by scope"), so a
filter bug is distinguishable from a genuine miss.
- Add a regression test: solo-repo → track second repo → untrack → CLI
search_text for a known literal must return >0.
Environment
daemon statusshowssearch sqlite-fts5 docs=72707(so the text index exists and is populated)
Repro (100% reproducible here)
Control facts:
search_symbolsfindssetupOutboxDispatcherinstantly (it exists atservices/domain-core/cmd/dispatcher/main.go:388).git grep -c "CREATE TABLE"→ 39 files underdb/migrationsalone.So this is not "term not indexed" — every query returns zero, and the
scope_note's suggested escape hatch (repo:"*") also returns zero, whichmakes the note actively misleading.
Confirmed repro — broken even in SOLO mode on a fresh daemon
Isolated home, fresh store, ONE tracked repo of 3 files containing the
literal
UNIQUE_LITERAL_ALPHA_7391in an indexed.gofile (verified viasearch_symbolsfinding its enclosing function):So this is not an artifact of our production daemon's history — the CLI
search_text path returns zero in the most basic configuration possible.
Note the scope resolves to
repo:repo-awhile solo-mode matches carryunprefixed paths (see #309): if the scope filter compares
match-path prefixes against
RepoAllow, every solo match fails the test.repo:"*"also returning 0 suggests the wildcard from the scope_note hint istaken as a literal repo name rather than a widen.
Why this matters more than a broken verb
Agents are told zero results might mean "does not exist". A search verb that
returns zero for everything — while suggesting a widen that doesn't work —
manufactures false "X does not exist" conclusions. (We caught it only by
running a known-positive control; that control discipline is not something
every agent does.)
Diagnostic clue (from reading
internal/mcp/tools_search_text.go)Matches flow through
filterTextMatchesByPath(..., repoPrefixes)and thenfilterTextMatchesByResolvedScope(matches, resolved). Combined with thecompanion dual-identity issue (repo registered as name `` at one layer and
domain-manageat another), the likely failure is:form:
services\...),resolved.RepoAllowcarries the other (domain-manage),count=0,repo:"*"may not reach this session-bound resolver at all("session-bound sessions may only name their own workspace").
Proposed fix
filterTextMatchesByResolvedScope, treat a match whose path resolvesinside a repo allowed by
RepoAllowas allowed regardless of which IDform the path carries (normalize both sides to absolute path or to one
canonical prefix before comparing).
say so in
scope_note(e.g."N matches were excluded by scope"), so afilter bug is distinguishable from a genuine miss.
search_text for a known literal must return >0.