In a git worktree the index cannot read the lane's own files: a relative path resolves against the PRIMARY root and fails with a raw internal_error, not the documented path_outside_known_roots #201
Labels
No labels
code-review
correctness
dos
performance
security
severity/high
severity/low
severity/medium
tech-debt
Kind/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
h-dv/code-index#201
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found by a lane working #41/#45/#51, and it forced that lane onto
grepfor code it had just written. Worktrees are the standing workflow here, so this is the common case rather than an edge one.Measured
From inside a git worktree, asking for a lane-local file by relative path:
The path is correct relative to the worktree. The server resolved it against the primary root, where that file does not exist — the lane created it in its own worktree.
Two separate problems, and the second is the one to fix first
1. The routing. Relative paths resolve against primary. For a session whose working directory is a worktree, every relative path to its own new work is unresolvable, and the tool is unusable for exactly the files the lane is being asked to reason about. Absolute paths auto-route by longest root-prefix and are unaffected — but an agent naturally types the relative path it just used with
Write.2. The error shape is wrong, and this is the cheap, high-value half. The documented, designed behaviour for a path the server cannot place is
path_outside_known_roots, carryingdid_you_meanwith the known roots and a hint about[[links]]in.code-index.toml. That is a corrective error: it tells the caller what happened and what to do.What comes back instead is a raw
internal_errorwith acanonicalize failedmessage and an absolute path the caller never typed. That reads as a server malfunction, not as a routing miss. An agent seeinginternal_errorreasonably concludes the tool is broken and falls back to the shell — which is precisely what happened, in a repo whose CLAUDE.md makes preferring the index a hard rule.Note the contrast, because it shows the corrective path already exists and is good: querying an unlinked corpus path gives
path_outside_known_rootswithdid_you_mean: ["primary=/home/master/code/rust/cosi-mcp"]. Same underlying situation — a path the server cannot place — with a completely different quality of answer. The worktree case simply misses that branch and falls through to a generic failure.Why existing gates do not catch it
Every test creates a fixture project and queries it as primary. No test queries an index from a working directory that is a git worktree of the indexed root, so the configuration is unrepresentable in the harness — the same structural blindness recorded for #181 (binary vs tree skew) and #182 (which tree was read).
That matters here because the project's own policy is that lanes work in worktrees. The one configuration the team uses all day is the one configuration nothing tests.
What must NOT be done
internal_error; the honest minimum is that the error says so.canonicalizedetail. It is the actual cause and belongs in the corrective error's body — just not as the whole of it.Suggested shape, in priority order
path_outside_known_rootswithdid_you_meannaming the known roots, and say plainly that relative paths resolve against primary. Cheap, no behaviour change, and it converts a dead end into a next step.Related
#182 (which tree produced the answer — any routing fix must not reintroduce it), #191 (the same shape for the pinned corpus: capability exists, discoverability does not), #175.
Reported by a lane that then had to
grepits own new file, 2026-09-06, againstcode-index-mcp 0.26.1 (8d90075).Fixed and released in v0.27.0 via
730bf15. Both stated problems are addressed, and I verified the remedy live rather than reading the diff.Problem 2 (the error shape) — fixed, and it was hiding a deeper bug
The valuable part is what the fix found. The issue framed this as a worktree problem; it reproduced against a plain typo, so it was never about worktrees at all. It was a daemon/snapshot parity break of exactly the shape I035 is written about:
LocalIndex::read_spananswers a missing fileOk(None)— the contractread_code'sfile_not_readablearm is written against. Under the daemon that entire arm was unreachable, because the RPC handler canonicalized first and turned every ENOENT intoinvalid_params. Every existing test read a file that exists, so nothing could see it.ENOENT now answers null on both legs (the bound check still runs on every path that resolves, and every other canonicalize error still fails loudly). Mutation M3 (restore the daemon's ENOENT
invalid_params) → RED, daemon leg ONLY — which is the proof the parity runner was the thing missing, not the arm.The corrective error now names WHICH ROOT it looked under and why a relative path landed there, from ONE helper shared by
read_code,file_outlineandindex_coverage. Error paths only, so a successful call pays nothing. M4 (relative_path_routing_notenever fires) → RED; M5 (dropwith_did_you_meanfromno_outline) → RED.Problem 1 (the routing) — answered by decision, and the remedy is real
Relative paths still resolve against the routed project's root, never the caller's working directory, and the server now says so along with two remedies. That is the right call: the MCP server has no reliable view of a client's cwd, and silently reinterpreting relative paths per-session would make identical requests mean different things.
I checked that the stated remedy actually works, because a remedy in an error message is a claim like any other. From this session, against a live lane worktree:
It routed to primary by longest root prefix and read the file off disk. So for the exact call in this issue's repro, passing an absolute path is a working answer, not just advice.
What does NOT work, split out as its own issue
read_codeserves bytes off disk and does not need the file indexed. The search and symbol tools do, and a worktree here lives under.claude/worktrees/, which is permanently excluded:So the original lane's actual complaint — "it forced that lane onto
grepfor code it had just written" — is only half-answered: it can nowread_codeits own files, but it cannotsearch_symbolsorsearch_textthem. That is a distinct defect with a distinct fix (and the obvious fix, allowlisting.claude/worktrees/, is the wrong one — it would fan every symbol out N times across N concurrent lanes). Filed separately so it does not ride along on a closed issue.Closing this one: both problems it states are resolved.
.claude/, which is permanently unindexable #238no_outlineandpath_not_indexedframe a PERMANENT exclusion as transient, and send the caller to a tool that cannot answer either #243