Files
codegraph/docs/benchmarks/explore-noise-epic-cg24.md
T
Colby McHenryandClaude Opus 5 07338ff12e docs(benchmarks): record CG-38 as open, and correct the regression claim
The epic record said nothing was open. CG-38 is: agent-named symbols in the
tail of a large file never render, which the epic's probes cannot see because
none of them measures whether the named symbol appeared.

Also corrects a wrong claim made while investigating it. The epic was said to
have regressed its own motivating query; that comparison varied the index as
well as the engine. A controlled bisect holding the index fixed shows the
pre-epic engine rendering 12 lines and CG-36 rendering 463 — the epic strictly
improves the case, and the symbols render at neither.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 20:29:14 -05:00

120 lines
7.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Epic resolution — explore response noise (CG-24)
Worked 2026-08-05 → 2026-08-06. Started from one bad `codegraph_explore` response
in a real session and ended with four shipped fixes, one open defect, and five
issues closed because measurement contradicted them.
**The headline: the reported symptom was not an explore bug.** It was a degraded
index. The explore defects the investigation found are real and were fixed, but
none of them caused the report.
## The report
A prose flow query returned an unusable response: the symbol the agent had named
never rendered, and a 12k-line generated Cloudflare ambient-types file took 60.7%
of the output envelope.
```
# deliv% bytes reserved score pen flags file
1 1.0% 251 10,970 87.0 1.00 named entry central <the named file>
2 60.7% 15,043 6,484 49.0 1.00 entry central worker-configuration.d.ts
4 — — — 19.9 1.00 dropped: budget <a third file>
```
## Root cause
**Index drift ([CG-33](index-drift-cg33.md)).** The live incrementally-synced index
diverged from a clean rebuild by 4.3% of distinct edges, bidirectionally,
overwhelmingly `calls`. RWR graph mass is relative and normalized, so call edges
missing elsewhere inflate an unaffected file's share — the `.d.ts` carried mass
0.24750 drifted vs 0.13119 rebuilt (~1.9×), score 49.0 vs 27.0.
Two causes, both fixed: incremental sync re-resolved only references *in* changed
files, and `getNodesByName` had no `ORDER BY`, so ties broke by rowid — i.e. by
the order files happened to be **written**. The second is why scope alone could
never converge. Stale edges dropped 671 → 2 across an 80-commit replay.
On a freshly rebuilt index the reported query answers correctly **with no explore
change at all**.
## Shipped
| | what |
|---|---|
| **CG-30** | Bounded how far an oversize cluster member may overshoot; windows on whole lines past 1.5× instead of emitting whole — or, when larger than the response ceiling, dropping the file silently. |
| **CG-31** | Gave the cluster path the `owedBelow` displacement guard the whole-file BUY arm always had, holding back only the prefix of what is owed below that the response can actually pay. |
| **CG-26** | Closed the remaining holes: whole-file arms had no displacement guard at all, section overhead was charged at a flat 200 against a real 300500, and `owedPayableBelow` held all-or-nothing. |
| **CG-25** | Recognize `Generated by <tool> by running <command>` banners. Precision held by requiring two `by` clauses, so ordinary prose does not match. |
| **CG-28** | Damp declaration-only files that nothing in the index depends on. Does not stack with the generated penalty (`Math.min`), and naming a declaration symbol exempts its file. |
| **CG-33 / CG-35** | Incremental sync converges with a rebuild, plus a regression suite that fails when the fix is disabled. |
| **CG-36** | A later cluster is shrunk into the remainder rather than dropped whole — at selection, and again in the ceiling trim. All 8 starvation flags across the suite clear; +1,012 source chars net. `explore-cluster-starvation-cg36.md`. |
Deterministic across the 6-repo suite: no repo truncates, none loses a file,
okhttp gains one, every repo lands at or under the 25,000 hard ceiling.
## Open
**CG-38** — agent-named symbols in the tail of a large file never render. On the
motivating repo, `queueMessage` (line 1087) and `flushQueuedMessages` (1102) in a
1,414-line file are absent from the response on both prose and symbol-bag
queries, even when that file wins rank #1 with 67% of the envelope. The response
returns the `QueuedMessage` *interface* at line 70 — a fuzzy near-match on the
query token — instead of the function.
**Pre-existing, not caused by this epic.** A controlled bisect (index held fixed,
engine varied across every merge point) shows the pre-epic engine rendering 12
lines here and CG-36 rendering 463; the symbols render at neither. The epic
strictly improves the case. An earlier claim that the epic regressed it was
wrong — it compared runs across two different indexes.
Sharpest lead: an earlier index of the same repo with the `.d.ts` **not** flagged
generated rendered 581 lines including both symbols on the pre-epic engine, where
the current flagged index renders 12. A penalty on one file should not shrink an
unrelated top-ranked file's render; `rankPenalty` scales `fileGraphScore`, which
moves the relevance gate and reshuffles the admitted set.
This epic's probes measure envelope share, starvation, source totals and file
counts. **None measures "did the agent-named symbol render"** — which is why this
survived the whole epic. CG-38 requires the fixture that closes that gap.
CG-36's own measurement is worth carrying forward, because the issue named the wrong
fix point: both real cases (`query.py`, `RealInterceptorChain.kt`) lost on
`maxImportance`, **not** on the density tiebreak the issue suspected. Ranking was
left alone; the never-shrink rule was the lever. Full numbers and the one cost
(okhttp trades its rank-6 file for +7,196 chars in the two that answer the
question) in `explore-cluster-starvation-cg36.md`.
## Closed because measurement contradicted them
Five, which is the story of this epic as much as the fixes are.
| | why |
|---|---|
| **CG-32** | Named file "didn't render first." Drift artifact; on a clean index it renders first and takes 89%. |
| **CG-34** | "Allocator over-reserves for low-scoring files." Filed on a runner's diagnosis without checking the numbers. The file was never over-reserved (4,314 in both arms) — it was over-*spending*, which is CG-31. |
| **CG-27** | Adding `function`/`method` to `ENVELOPE_KINDS` measured as a **large regression** — rank #1 fell from 7,539 delivered chars to 397, 7 of 11 inner closures to 0. The enclosing range was holding the file together as one cluster, inside which `shrinkCluster` already did the per-symbol ranking the issue wanted. A careful version was noise (69 vs 68 across nine queries). |
| **CG-29** | Prose-vs-symbol query gap. Inverted on measurement: prose matches symbol on django and delivers 63% more source on okhttp. The founding observation was drift. |
| **CG-37** | Duplicate of CG-36, filed without seeing it. |
## What this epic is actually a lesson in
**A confident diagnosis is worth less than a cheap measurement.** Every issue
above was filed by someone — human or agent — who had read the code and had a
plausible mechanism. Five were wrong. The ones that survived did so because a
deterministic probe disagreed with them and the probe won.
Two specific traps this cost real time on, both now guarded in tooling:
- **`.codegraph/graph.db` does not exist** — the index is `codegraph.db`, and
`sqlite3` against a mistyped path *creates* an empty database rather than
failing. An empty schema reads exactly like a stale pre-migration index. This
produced a wrong root cause. `diff-index-drift.mjs` refuses a missing path.
- **`ab-new-vs-baseline.sh` checks the engine out at the baseline ref mid-run.**
A commit made while it runs captures baseline sources and silently reverts the
fix under test. This happened during CG-30. Check the `changed:` line before
believing any A/B result.
And one measurement discipline worth keeping: **compare sets, not totals.** The
drift that started all of this shows up as +0.7% on raw edge counts, because it
is bidirectional and nets out. On distinct edge triples it is 4.3%.