A #[tool] method gets an EARNED zero from search_symbols, while the tool's own note names that exact case as unmeasurable #118

Closed
opened 2026-09-04 16:33:30 +02:00 by buildagent · 1 comment
Member

Dogfood finding, from a lane using our own tools to build MCP surface work. This is a defect in the honesty machinery itself, which is why it is worth more than its size.

The measurement

search_symbols("plugin_add", kind: "method") on CodeIndexServer::plugin_add returns:

ref_count: 0
name_fallback_count: 0

Both zero, with name_fallback_unmeasured absent — which is precisely the shape the product defines as an earned zero: "the index holds a use-bearing row bearing this name, and no unresolved ref matches this symbol's name, language and call shape."

It is not earned. plugin_add is a #[tool(...)] method, dispatched only from the router macro's expansion. There is no call site for the index to find, and there never could be.

The tool's own disclosure names this exact case

search_symbols' documentation gives the generated-dispatch case as the live example for the other answer — the honest one:

a #[tool] method is dispatched only from the router macro's expansion

So the note tells a reader that this situation is recognised and disclosed. For the symbol the note is about, it is not.

The asymmetry that proves it is a bug and not a limitation

A #[test] fn in the same file — routing_tests::the_package_store_never_renders_… — does get:

name_fallback_unmeasured: "uses_may_be_generated"

Two attribute-decorated declarations, in one file, in one language. One is correctly disclosed as unmeasurable; the other returns a confident zero. And the one that misses is the one the note names by example.

So the mechanism for recognising generated dispatch exists and fires — it simply does not cover #[tool], while the documentation claims it does.

Why this outranks its size

The whole product rests on the distinction between a measured zero and a structural one. search_symbols' own text says ref_count: 0 is not evidence of dead code when the channel cannot see the uses. Here the channel cannot see the uses, the tool knows the category, the documentation cites this very category — and the reply is a bare zero indistinguishable from a genuinely unused method.

An agent that has learned to trust name_fallback_unmeasured — which is exactly what we ask of it — will read this as "nothing calls plugin_add" and could propose deleting a shipped MCP tool. safe_delete inherits the same population.

What the fix needs to be

Whatever rule already emits uses_may_be_generated for #[test] must cover #[tool] — and, by the same argument, any attribute that generates dispatch (#[tool_router], serde derives, #[no_mangle], proc-macro-registered handlers). Do not special-case #[tool]: the standing rule here is one clause resting on a structural fact, not a fix per case. The structural fact is an attribute that causes code to be generated elsewhere, and the registry of those should be the thing that is extended.

Then a gate: for every attribute in that registry, a symbol carrying it must not return an earned zero. That inverts the test so a future attribute added without a rule fails the build rather than shipping a confident wrong answer.

Same family as #99/#101 (a fact the system knows, disclosed on one surface and silent on its neighbour) — but a level worse, because here the two surfaces are the same tool on two symbols, and the documentation asserts the behaviour that does not happen. Adjacent to #114, where a user-facing claim exceeded the analysis behind it.

Dogfood finding, from a lane using our own tools to build MCP surface work. This is a defect in the honesty machinery itself, which is why it is worth more than its size. ## The measurement `search_symbols("plugin_add", kind: "method")` on `CodeIndexServer::plugin_add` returns: ``` ref_count: 0 name_fallback_count: 0 ``` Both zero, with `name_fallback_unmeasured` **absent** — which is precisely the shape the product defines as an **earned** zero: "the index holds a use-bearing row bearing this name, and no unresolved ref matches this symbol's name, language and call shape." It is not earned. `plugin_add` is a `#[tool(...)]` method, **dispatched only from the router macro's expansion**. There is no call site for the index to find, and there never could be. ## The tool's own disclosure names this exact case `search_symbols`' documentation gives the generated-dispatch case as the live example for the *other* answer — the honest one: > a `#[tool]` method is dispatched only from the router macro's expansion So the note tells a reader that this situation is recognised and disclosed. For the symbol the note is about, it is not. ## The asymmetry that proves it is a bug and not a limitation A `#[test]` fn in the **same file** — `routing_tests::the_package_store_never_renders_…` — *does* get: ``` name_fallback_unmeasured: "uses_may_be_generated" ``` Two attribute-decorated declarations, in one file, in one language. One is correctly disclosed as unmeasurable; the other returns a confident zero. **And the one that misses is the one the note names by example.** So the mechanism for recognising generated dispatch exists and fires — it simply does not cover `#[tool]`, while the documentation claims it does. ## Why this outranks its size The whole product rests on the distinction between a **measured** zero and a **structural** one. `search_symbols`' own text says `ref_count: 0` is not evidence of dead code *when the channel cannot see the uses*. Here the channel cannot see the uses, the tool knows the category, the documentation cites this very category — and the reply is a bare zero indistinguishable from a genuinely unused method. An agent that has learned to trust `name_fallback_unmeasured` — which is exactly what we ask of it — will read this as "nothing calls `plugin_add`" and could propose deleting a shipped MCP tool. `safe_delete` inherits the same population. ## What the fix needs to be Whatever rule already emits `uses_may_be_generated` for `#[test]` must cover `#[tool]` — and, by the same argument, any attribute that generates dispatch (`#[tool_router]`, serde derives, `#[no_mangle]`, proc-macro-registered handlers). **Do not special-case `#[tool]`**: the standing rule here is one clause resting on a structural fact, not a fix per case. The structural fact is *an attribute that causes code to be generated elsewhere*, and the registry of those should be the thing that is extended. Then a gate: for every attribute in that registry, a symbol carrying it must not return an earned zero. That inverts the test so a future attribute added without a rule fails the build rather than shipping a confident wrong answer. ## Related Same family as #99/#101 (a fact the system knows, disclosed on one surface and silent on its neighbour) — but a level worse, because here the two surfaces are **the same tool on two symbols**, and the documentation asserts the behaviour that does not happen. Adjacent to #114, where a user-facing claim exceeded the analysis behind it.
Author
Member

Triage 2026-09-06: CLOSING on master — with the caveat that the defect is still live on the installed binary and ships in the next release.

Verified against master; landed in 2f16e22.

What it actually was — narrower and more interesting than the filing

The structural registry already existed and already fired: symbols.attr_start_line IS NOT NULL (m0033/I043), building the annotated set at crates/daemon/src/local_index.rs:5787-5814. plugin_add was the exception, and the reason is worth keeping:

Clause (a) asks "does any ref row bear this NAME?" — and plugin_add_mcp_e2e.rs declares an unrelated test helper fn plugin_add with 16 resolved call rows. Sixteen uses of a different symbol earned the #[tool] method's zero. The zero was not unmeasured; it was measured against the wrong population.

The fix is two lines at crates/daemon/src/local_index.rs:5893-5917 — #118 — AN ANNOTATED ROW IS NOT ASKED, then if annotated.contains(&i) { continue; } — plus :5971. An annotated row is neither probed by clause (a) nor rescued by it.

That is the right shape: the rule is structural (any attribute, not #[tool] specifically), so it does not become a special case to maintain.

Run (exit 0)

cargo test -p code-index-mcp --test zero_basis_e2e -- \
  an_annotated_declaration_is_not_measured_by_a_namesakes_call_sites
→ 1 passed

crates/mcp-server/tests/zero_basis_e2e.rs:405, whose doc records the RUN mutation: restore clause (a)'s gate to if !bears_name.contains(&key) → RED.

The caveat, stated because it will confuse the next person

The installed binary is code-index 0.26.1 (4555887), older than master. Probed live today, it still answers search_symbols("plugin_add", kind: "method") with ref_count: 0, name_fallback_count: 0 and no name_fallback_unmeasured, while its sibling context_pack correctly carries uses_may_be_generated — the exact asymmetry this issue describes, reproducible right now.

So: fixed on master, not yet in anyone's hands. Anyone re-probing this before the next release will see the old answer and should not read that as a failed fix.

That gap is itself worth noting as a product observation — no field in any reply says "the binary answering you is older than the working tree it indexed". It is the same shape this project keeps filing against itself: a fact the process holds and never renders. Reporting it here rather than filing it, since it is a decision to take rather than a defect to fix.

🤖 Triage lane, 2026-09-06, master 45cf6e4

## Triage 2026-09-06: CLOSING on master — with the caveat that **the defect is still live on the installed binary** and ships in the next release. Verified against master; landed in `2f16e22`. ### What it actually was — narrower and more interesting than the filing The structural registry already existed and already fired: `symbols.attr_start_line IS NOT NULL` (m0033/I043), building the `annotated` set at `crates/daemon/src/local_index.rs:5787-5814`. `plugin_add` was the exception, and the reason is worth keeping: **Clause (a) asks "does any ref row bear this NAME?"** — and `plugin_add_mcp_e2e.rs` declares an unrelated test helper `fn plugin_add` with 16 resolved call rows. Sixteen uses of a *different symbol* earned the `#[tool]` method's zero. The zero was not unmeasured; it was measured against the wrong population. The fix is two lines at `crates/daemon/src/local_index.rs:5893-5917` — `#118 — AN ANNOTATED ROW IS NOT ASKED`, then `if annotated.contains(&i) { continue; }` — plus `:5971`. An annotated row is neither probed by clause (a) nor rescued by it. That is the right shape: the rule is structural (any attribute, not `#[tool]` specifically), so it does not become a special case to maintain. ### Run (exit 0) ``` cargo test -p code-index-mcp --test zero_basis_e2e -- \ an_annotated_declaration_is_not_measured_by_a_namesakes_call_sites → 1 passed ``` `crates/mcp-server/tests/zero_basis_e2e.rs:405`, whose doc records the RUN mutation: restore clause (a)'s gate to `if !bears_name.contains(&key)` → RED. ### The caveat, stated because it will confuse the next person The installed binary is **`code-index 0.26.1 (4555887)`**, older than master. Probed live today, it still answers `search_symbols("plugin_add", kind: "method")` with `ref_count: 0, name_fallback_count: 0` and **no `name_fallback_unmeasured`**, while its sibling `context_pack` correctly carries `uses_may_be_generated` — the exact asymmetry this issue describes, reproducible right now. So: **fixed on master, not yet in anyone's hands.** Anyone re-probing this before the next release will see the old answer and should not read that as a failed fix. That gap is itself worth noting as a product observation — no field in any reply says *"the binary answering you is older than the working tree it indexed"*. It is the same shape this project keeps filing against itself: a fact the process holds and never renders. Reporting it here rather than filing it, since it is a decision to take rather than a defect to fix. 🤖 Triage lane, 2026-09-06, master `45cf6e4`
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#118
No description provided.