read_code says an empty body "PROVES the range does not exist" — but it proves it about the SERVER's tree, which is not the caller's when a worktree is in play #258

Closed
opened 2026-09-10 17:09:27 +02:00 by buildagent · 1 comment
Member

Found by a worktree-isolated agent working on this repository — which is this project's own primary working pattern, so the blast radius is us.

The claim

crates/mcp-server/src/server.rs:16743:

requested_range_past_end_of_file: nothing was served, so `start_line` and `end_line`
echo your REQUEST rather than describing content. More than one line was requested,
so an empty body PROVES the range does not exist — `file_outline` reports the file's
real extent.

PROVES is unqualified. It is a strong word chosen deliberately — the surrounding comment shows the note was written precisely to resolve an ambiguity, and v0.19.3's version of it was found stating that ambiguity backwards.

Why it is false in a context the tool cannot see

The bytes are read live from disk at request time — but from the server's project root. An agent working in a git worktree (.claude/worktrees/agent-*) asks for a relative path and gets an answer about the primary checkout's copy of that file, not its own.

Measured, by the agent that hit it: a read_code of a range that exists in its file returned requested_range_past_end_of_file. Its own edits were invisible; the file it was shown was a different file with the same relative path. The note told it the range had been proven not to exist.

The same applies to search_text and every symbol lookup — an agent cannot dogfood the tools on its own edits — but only read_code phrases its answer as a proof.

The information already exists, one field away

Every reply carries it:

"answer_provenance": {
  "indexed_trees": { "primary": { "head": "05a38166a76c", "uncommitted_changes": true,
                                  "sibling_worktrees": 4 } }
}

The server KNOWS there are sibling worktrees and which head it served. The provenance block and the proof-claim simply are not connected — which is this repo's own recurring shape: the disclosure is built, and then not attached to the sentence that needs it.

Shape of a fix

Not "delete the word PROVES" — the strong claim is correct and useful in the ordinary single-tree case, and weakening it everywhere would cost the reader the thing the note was written to give them.

Attach the scope instead. When sibling_worktrees > 0, the proof is about a named tree and must say so — the head it served is already in hand, so the note can carry it:

…an empty body proves the range does not exist in the tree this server indexes (primary @ 05a38166); if you are working in a sibling worktree, this is not a statement about your copy.

That is one clause, gated on a fact the reply already computes, and it leaves the single-tree wording unchanged.

What must be graded

  • a fixture with sibling_worktrees > 0 gets the scoped wording, one with 0 gets the unscoped wording — both directions, since a note that is always scoped is as wrong as one that never is;
  • MUTATION: make the scoped arm unconditional → the single-tree fixture must go RED. Without that arm the "fix" is a string change nothing grades.

Same family as #243 (a disclosure may not name a remedy that cannot answer it) and #254 (a discriminator built and then dropped before the record). The recurring lesson is that the disclosure exists and is not wired to the claim it qualifies.

Found by a worktree-isolated agent working on this repository — which is this project's own primary working pattern, so the blast radius is us. ## The claim `crates/mcp-server/src/server.rs:16743`: ``` requested_range_past_end_of_file: nothing was served, so `start_line` and `end_line` echo your REQUEST rather than describing content. More than one line was requested, so an empty body PROVES the range does not exist — `file_outline` reports the file's real extent. ``` **PROVES** is unqualified. It is a strong word chosen deliberately — the surrounding comment shows the note was written precisely to resolve an ambiguity, and v0.19.3's version of it was found stating that ambiguity backwards. ## Why it is false in a context the tool cannot see The bytes are read live from disk at request time — but from the **server's** project root. An agent working in a git worktree (`.claude/worktrees/agent-*`) asks for a relative path and gets an answer about the *primary* checkout's copy of that file, not its own. Measured, by the agent that hit it: a `read_code` of a range that **exists in its file** returned `requested_range_past_end_of_file`. Its own edits were invisible; the file it was shown was a different file with the same relative path. The note told it the range had been *proven* not to exist. The same applies to `search_text` and every symbol lookup — an agent cannot dogfood the tools on its own edits — but only `read_code` phrases its answer as a proof. ## The information already exists, one field away Every reply carries it: ```json "answer_provenance": { "indexed_trees": { "primary": { "head": "05a38166a76c", "uncommitted_changes": true, "sibling_worktrees": 4 } } } ``` The server KNOWS there are sibling worktrees and which head it served. The provenance block and the proof-claim simply are not connected — which is this repo's own recurring shape: the disclosure is built, and then not attached to the sentence that needs it. ## Shape of a fix Not "delete the word PROVES" — the strong claim is correct and useful in the ordinary single-tree case, and weakening it everywhere would cost the reader the thing the note was written to give them. Attach the scope instead. When `sibling_worktrees > 0`, the proof is about a *named* tree and must say so — the head it served is already in hand, so the note can carry it: > …an empty body proves the range does not exist **in the tree this server indexes (`primary` @ `05a38166`); if you are working in a sibling worktree, this is not a statement about your copy**. That is one clause, gated on a fact the reply already computes, and it leaves the single-tree wording unchanged. ## What must be graded * a fixture with `sibling_worktrees > 0` gets the scoped wording, one with `0` gets the unscoped wording — both directions, since a note that is always scoped is as wrong as one that never is; * MUTATION: make the scoped arm unconditional → the single-tree fixture must go RED. Without that arm the "fix" is a string change nothing grades. ## Related Same family as #243 (a disclosure may not name a remedy that cannot answer it) and #254 (a discriminator built and then dropped before the record). The recurring lesson is that the disclosure exists and is not wired to the claim it qualifies.
Author
Member

Fixed. And I did not implement the design proposed above — the simpler one is better, so here is the reasoning rather than a silent substitution.

What the proposal asked for, and why I dropped it

The issue proposed gating the scoped wording on sibling_worktrees > 0, so a single-tree deployment keeps the unqualified claim. That needs the sibling count at the note site. It is reachable — CodeIndexServer::tree_states caches a RootProvenance per root behind a TTL — but it arrives as three states, not two: measured-zero, measured-nonzero, and unmeasured-or-not-yet-cached. Getting that right means the unqualified proof may only be emitted on a MEASURED zero, since "absent is not zero" is this repo's rule. That is new state to keep honest, two fixtures to maintain, and a cache read on a path that exists only when a read returns nothing.

The gain it buys is that single-tree users are spared one clause. Weighed against it: naming which tree answered is useful to every caller, not a caveat — it tells them where to look next — and it is unconditionally TRUE, so there is no state to get wrong.

What shipped

requested_range_past_end_of_file: nothing was served, so `start_line` and `end_line`
echo your REQUEST rather than describing content. More than one line was requested,
so an empty body proves the range does not exist IN THE TREE THIS SERVER INDEXES,
which `answer_provenance.indexed_trees` names — if you are reading a sibling worktree,
this is not a statement about your copy. `file_outline` reports that tree's real extent.

PROVES became proves … IN THE TREE THIS SERVER INDEXES. The claim keeps its force and gains its scope.

Cost is on a path that only exists when nothing was served, so it is not the sibling_worktrees situation the provenance module documents — always-emitting "sibling_worktrees":0 was measured at 578 tokens, 5.2% on the plugin-wpf bench and tripped the 1.05x ceiling. This note is emitted only on an empty multi-line read; normal replies are unchanged, and the bench is green.

Graded, with the mutation run

mcp_smoke::read_code_past_eof_says_so_instead_of_inverting_the_range already asserted the note's PREFIX, which could not see the scope. It now also asserts the note contains indexed_trees — the field name, not the prose, because the reader's next action is to look at that field and a field name is stable where a turn of phrase is not.

MUTATION (RUN): restore the unqualified PROVES the range does not exist —.
RESULT: RED, md5 a9d37d69… → 26d35fb3…, with the failure printing the offending note in full. Restored; md5 back to a9d37d69….

One detail the mutation output surfaced: in that fixture the provenance reads indexed_trees: {"primary": {"unavailable": "not_a_git_checkout"}}. So the pointer stays informative even where the tree is not a checkout at all — it says so, rather than leaving the reader to assume.

What this does not fix

search_text and search_symbols have the same underlying scope — a worktree-isolated caller gets answers about the primary checkout — but neither phrases its answer as a proof, which is what made this one actively misleading rather than merely narrow. That the tools cannot be dogfooded on a lane's own edits is a real cost of the worktree workflow and remains open; it is a routing question, not a wording one.

Fixed. **And I did not implement the design proposed above** — the simpler one is better, so here is the reasoning rather than a silent substitution. ## What the proposal asked for, and why I dropped it The issue proposed gating the scoped wording on `sibling_worktrees > 0`, so a single-tree deployment keeps the unqualified claim. That needs the sibling count at the note site. It is reachable — `CodeIndexServer::tree_states` caches a `RootProvenance` per root behind a TTL — but it arrives as **three** states, not two: measured-zero, measured-nonzero, and unmeasured-or-not-yet-cached. Getting that right means the unqualified proof may only be emitted on a MEASURED zero, since "absent is not zero" is this repo's rule. That is new state to keep honest, two fixtures to maintain, and a cache read on a path that exists only when a read returns nothing. The gain it buys is that single-tree users are spared one clause. Weighed against it: naming which tree answered is **useful to every caller**, not a caveat — it tells them where to look next — and it is unconditionally TRUE, so there is no state to get wrong. ## What shipped ``` requested_range_past_end_of_file: nothing was served, so `start_line` and `end_line` echo your REQUEST rather than describing content. More than one line was requested, so an empty body proves the range does not exist IN THE TREE THIS SERVER INDEXES, which `answer_provenance.indexed_trees` names — if you are reading a sibling worktree, this is not a statement about your copy. `file_outline` reports that tree's real extent. ``` `PROVES` became `proves … IN THE TREE THIS SERVER INDEXES`. The claim keeps its force and gains its scope. **Cost is on a path that only exists when nothing was served**, so it is not the `sibling_worktrees` situation the provenance module documents — always-emitting `"sibling_worktrees":0` was measured at **578 tokens, 5.2%** on the `plugin-wpf` bench and tripped the 1.05x ceiling. This note is emitted only on an empty multi-line read; normal replies are unchanged, and the bench is green. ## Graded, with the mutation run `mcp_smoke::read_code_past_eof_says_so_instead_of_inverting_the_range` already asserted the note's PREFIX, which could not see the scope. It now also asserts the note contains `indexed_trees` — the **field name**, not the prose, because the reader's next action is to look at that field and a field name is stable where a turn of phrase is not. MUTATION (RUN): restore the unqualified `PROVES the range does not exist —`. RESULT: **RED**, md5 `a9d37d69…` → `26d35fb3…`, with the failure printing the offending note in full. Restored; md5 back to `a9d37d69…`. One detail the mutation output surfaced: in that fixture the provenance reads `indexed_trees: {"primary": {"unavailable": "not_a_git_checkout"}}`. So the pointer stays informative even where the tree is not a checkout at all — it says so, rather than leaving the reader to assume. ## What this does not fix `search_text` and `search_symbols` have the same underlying scope — a worktree-isolated caller gets answers about the primary checkout — but neither phrases its answer as a *proof*, which is what made this one actively misleading rather than merely narrow. That the tools cannot be dogfooded on a lane's own edits is a real cost of the worktree workflow and remains open; it is a routing question, not a wording one.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
h-dv/code-index#258
No description provided.