A #[tool] method gets an EARNED zero from search_symbols, while the tool's own note names that exact case as unmeasurable #118
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#118
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?
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")onCodeIndexServer::plugin_addreturns:Both zero, with
name_fallback_unmeasuredabsent — 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_addis 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: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: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 saysref_count: 0is 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 callsplugin_add" and could propose deleting a shipped MCP tool.safe_deleteinherits the same population.What the fix needs to be
Whatever rule already emits
uses_may_be_generatedfor#[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.
read_coderejects the integer symbol id thatsearch_symbolsjust handed it, breaking the one documented zero-hop read #121change_impactreturns an empty, confident answer wherefind_callersfinds 5 call sites — and nothing in its payload says why #122page_name_fallback: 0with no shape-excluded sibling #173Triage 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 theannotatedset atcrates/daemon/src/local_index.rs:5787-5814.plugin_addwas the exception, and the reason is worth keeping:Clause (a) asks "does any ref row bear this NAME?" — and
plugin_add_mcp_e2e.rsdeclares an unrelated test helperfn plugin_addwith 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, thenif 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)
crates/mcp-server/tests/zero_basis_e2e.rs:405, whose doc records the RUN mutation: restore clause (a)'s gate toif !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 answerssearch_symbols("plugin_add", kind: "method")withref_count: 0, name_fallback_count: 0and noname_fallback_unmeasured, while its siblingcontext_packcorrectly carriesuses_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