The pinned corpus is unreachable through our own tools — resolution_gaps and every other MCP tool answer project_not_found, so a triage number can only be produced by running a benchmark suite #191
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#191
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 while tracing #175, whose entire case rests on a number that cannot be reproduced through the product.
Measured
project_overview()on primary reports no linked-projects block. The nine pinned repositories under$HOME/.cache/cosi-corpus/— the onescorpus_ratchet,corpus_stageandagent_task_benchall measure — are reachable by no MCP tool at all.The consequence, concretely
#175 is built on
resolution_gaps(python-flask) -> { name: "flask", ref_kind: "type", reason: "no_candidate", count: 559 }. To check that number this session had to:agent_task_bench(the only code path that indexes a corpus repo through the daemon), orsqlite3.Both were done. Neither is a tool call. And when the number was finally checked, three of its four claims were wrong: the 559 is the receiver
typerefs, not the calls (the call-shaped upper bound is 296); "216 files" is unattributable (the corpus has 83.pyfiles, 43 of which mentionflask.); and the reason code isreceiver_unbound, notno_candidate, for the population that actually costs recall.A number nobody can re-query is a number nobody re-checks. That is not a hypothetical — it is what happened, in the issue that motivated this one.
Why this is worth its own issue
grep,sqlite3and hand-built binaries.Repro
and confirm there is no
.code-index.tomlat the workspace root.What must NOT be done
.code-index.tomlwith absolute paths.$HOME/.cache/cosi-corpusis a per-machine location; CI and every other checkout would get a broken or silently-empty link, and a link that resolves to nothing is worse than none — it would makeproject_not_foundbecome "project present, zero results", which is the absence-is-not-a-state failure this repo keeps recording.search_symbols/search_textfan-out for every ordinary question, and cross-project contamination is exactly what the project-scoping design exists to prevent.COSI_CORPUS_DIR), or a--projectroute the server resolves on demand. Whatever the shape, an ABSENT corpus must keep answeringproject_not_foundrather than an empty success.Measured vs inferred
The tool error, the missing
.code-index.toml, the absent linked-projects block, and the corpus file counts are measured. That unverifiable numbers go unchecked is inferred — but supported by the three wrong claims in #175 that this session found only by leaving the tools.Related: #175 (whose headline number this concerns) and the
precision_gatepopulation issue filed alongside — both are cases where a claim's scope could not be checked with the product itself.🤖 Generated with Claude Code
https://claude.ai/code/session_01K1zj5VcFJvJt3pQxe9259K
CONFIRMED and FIXED on
lane/provenance(worktree/tmp/cosi-lane-provenance, based onfc329a8), in the shape your "what must NOT be done" list leaves standing: opt-in, explicitly local, via the env var the corpus tests already set.The mechanism
With
COSI_CORPUS_DIRset, every git checkout directly under it becomes a linked project named after its directory:Without it, nothing changes — including the error you quoted.
Nothing is committed. No
.code-index.tomlis written. The corpus does not enter the primary index.Your five prohibitions
"Do not link the corpus by committing a
.code-index.tomlwith absolute paths." Nothing is committed; the path comes from the operator's environment."Do not index the corpus into the primary index." They are separate projects, each with its own index.
a_linked_corpus_does_not_enter_the_primary_indexasserts the corpus symbol is reachable throughproject: "python-flask"and absent fromproject: "primary"in the same server, having first waited for the link to warm so the negative is not merely early."Do not close this by documenting the workaround." The tool call now works.
"Do not treat this as a request to make corpus links default … an ABSENT corpus must keep answering
project_not_foundrather than an empty success." This is the load-bearing one, and it is a paired test against the same fixture and the same project name:project_not_found, asserted by name.Either half alone is satisfiable by a stub.
an_unset_variable_links_nothing_and_skips_nothinggrades the same rule as a pure function, and its mutation — returning a placeholder link when the variable is unset — is exactly the "project present, zero results" state you name.Three states, not two
project_not_foundunchangedSkippedLinknaming the variable and the error, because an operator who set it and got nothing is owed the reasonA subdirectory without
.gitis not a project: the corpus directory is a cache and carries fetch scratch beside the checkouts. A corpus repo whose name collides with a[[links]]entry or withprimaryis skipped with a reason — the operator's own configuration wins over a cache directory, and a corpus repo that silently did not appear would be a mystery. Each link'sdescriptionnamesCOSI_CORPUS_DIRas its origin, soproject_overview.linked_projectssays where it came from and that on a machine without that variable the project does not exist.Mutations, run, all RED: placeholder link when unset; silent return on an unreadable directory; drop the
.gitguard (ascratch/dir becomes a project); shadow a configured name; never install the links into the server's project map (both e2e tests).What this broke, and the finding underneath it
COSI_CORPUS_DIRpreviously reached only the corpus suites. It now also reaches the MCP server — and CLAUDE.md's own verification recipe sets it oncargo test --workspace. Uncorrected, every one of the ~160Mcp::spawncalls inmcp-serverwould link nine large third-party repositories, each with its own daemon and cold index. The suite would still have been green; it would just have been measuring something else, slowly, under a name that says otherwise.support/store_home.rsnowenv_removes it from every product binary it spawns, andthe_harness_does_not_leak_the_corpus_variablegrades both halves — that the clear exists, and that the name it clears is the name the server reads, parsed out ofmain.rs. A rename on one side with a stale copy on the other is a leak that noenv_removeline can be inspected to catch. Mutations run: delete the clear (RED); change the harness's spelling toCOSI_CORPUS_DIRECTORY(RED — the clear would have been real and aimed at nothing).And that exposed a defect in
corpus_require_floor, which is the sharper finding here. Its detector marked any helper whose text contains$COSI_CORPUS_DIRas corpus-reaching. Declaring the name in order to remove it madestore_homereaching, thenmcp_command, then every mcp-server e2e binary: thirty listed "corpus consumers", none of which touch the corpus. The gate's premise is that a consumer self-skips when the variable is unset, and naming, clearing and supplying are three different relationships to a variable — none of them is reading it. The predicate is nowstd::env::var/var_osof the name, applied at all three sites that previously each spelled their own version, with the limit stated (a name passed across a crate boundary is still invisible). Mutations run: predicate always false → RED on the gate's own anti-vacuity test; predicate back tocontains→ RED on the consumer list.What is still not reachable
Only repositories directly under
$COSI_CORPUS_DIRare linked, and only when the operator sets it — so CI, and every checkout that has not runtests/corpus/fetch.sh, are unaffected by design. The#175number you could not re-query is now a tool call on a machine that has the corpus; it is still not one on a machine that does not, and that is the trade your own constraints require.reconcilingindex and pass in isolation: one signature, two victims, so it is a missing barrier and not a flake #192FIXED in
fb37a49, merged as4f866e5.corpus_link_e2egrades it.The pinned corpus is now reachable through our own MCP tools, and the two tests hold the design apart:
the_corpus_is_reachable_only_when_the_operator_opts_in— reachability is opt-in, not automatic. The corpus is a test fixture; making it visible by default would silently change what every project-scoped answer is computed over.a_linked_corpus_does_not_enter_the_primary_index— and this is the one that matters. A linked corpus must be queryable without becoming part of the primary index. If it merged in, every count, every census and everyresolution_gapsdenominator on a real project would silently include seven unrelated repositories, which is a worse problem than the one this issue reports.Closing.
Follow-up, because a lane hit this AFTER I closed it and I want the reason on record rather than a silent reopen-or-not.
A resolver lane doing a bind inspection across rust-analyzer and py-django reported:
read_codeon a corpus path answerspath_outside_known_roots,search_symbolsanswerssymbol_not_found, and every source read in that inspection had to be done withsed.That is not a regression, and the fix stands.
corpus_link_e2edoes not exist at8d90075, which is the commit the installedcode-index-mcp 0.26.1was built from — the binary serving these sessions predates the fix by design of when it was installed. This is exactly the skew #181/#182 shipped a disclosure for, and it is the second time today the installed binary has made a fixed thing look unfixed.Two things still owed, so this does not read as fully closed in practice:
sed, correctly.a_linked_corpus_does_not_enter_the_primary_indexkeeps the corpus out of primary's denominators, which matters more than the convenience. But there is no.code-index.tomlin the repo (correctly — the corpus path is per-machine), and nothing tells a lane how to link it. So the capability exists and is undiscoverable, which for an agent is close to not existing.Leaving this closed because the capability landed, and it is the right shape. If (2) turns out to keep costing lanes their bind inspections, that is a fresh issue about discoverability, not about this one.
internal_error, not the documentedpath_outside_known_roots#201.claude/, which is permanently unindexable #238