Follow-up to #10208, which identified grounding as the one genuinely underperforming cache: 15.9% hit rate (142 hits / 753 misses in 24h) against 74-86% for every other surface, while being the highest-volume one.
Root cause
getFileContent in src/review/grounding-wire.ts keys the cache on (repo, path, ref), where ref is the commit SHA. The comment there is correct that this is safe — a blob at an immutable commit never changes, so a hit is always reusable verbatim.
But it is keyed on the wrong immutable thing. Grounding reads context files (type declarations, generated schemas, manifests), which are usually not the files the PR changed. Those files are byte-identical across commits — yet every push mints a new ref, so every one of them misses again.
The cache is therefore invalidated by commits that did not touch the file it is caching.
Evidence (Orb, 24h to 2026-07-31T12:35Z)
|
|
| total grounding misses |
762 |
distinct (repo, path) pairs |
441 |
| misses that are a repeat read of a file already fetched |
321 (42%) |
Worst offenders — each row is one file, re-fetched from the GitHub Contents API this many times in a day:
| repo |
path |
misses/24h |
| JSONbored/metagraphed |
packages/contract/index.d.ts |
31 |
| JSONbored/metagraphed |
public/metagraph/openapi.json |
22 |
| JSONbored/metagraphed |
public/metagraph/api-index.json |
15 |
| JSONbored/metagraphed |
public/metagraph/types.d.ts |
15 |
| JSONbored/metagraphed |
apps/ui/src/routes/-subnets-netuid-page.tsx |
11 |
| JSONbored/loopover |
.release-please-manifest.json |
9 |
| JSONbored/loopover |
package.json |
7 |
| JSONbored/loopover |
apps/loopover-ui/public/openapi.json |
7 |
A generated index.d.ts did not change 31 times in a day. Each of those is a real Contents API call against the installation's REST quota — the same quota #10184 is separately trying to stop burning.
Recommended fix: conditional requests
Store the response ETag alongside the cached content under a (repo, path) key, and send If-None-Match when the ref-keyed lookup misses. GitHub answers 304 Not Modified for an unchanged file, and a 304 does not count against the REST rate limit. So:
- unchanged file, new commit → 304, reuse cached content, zero quota spent
- genuinely changed file → 200 with fresh content, exactly as today
- correctness is delegated to GitHub's own validator rather than to our guess about what changed
This keeps the existing (repo, path, ref) fast path untouched for a true same-ref hit.
A cheaper variant worth considering first, if ETag plumbing is too much: keep a (repo, path, ref) -> blobSha mapping plus a content-addressed (repo, path, blobSha) -> content store. That de-duplicates storage but still costs one call per new ref, so it does not save quota — the ETag route is the one that does.
Instrumentation note
Whatever lands should emit a distinct outcome for "revalidated 304" rather than folding it into either grounding_cache_hit or grounding_cache_miss — otherwise this cache acquires a fourth vocabulary for reuse, which is the exact problem #10208 is about.
Verification
All figures queried directly against the Orb Postgres primary. The repeat-read count is total misses - distinct (repoFullName, path) from audit_events where event_type = 'github_app.grounding_cache_miss', which is a lower bound on the waste: it counts a file re-fetched N times as N-1 avoidable calls only if all N shared content, which the top rows above make near-certain but do not strictly prove.
Follow-up to #10208, which identified
groundingas the one genuinely underperforming cache: 15.9% hit rate (142 hits / 753 misses in 24h) against 74-86% for every other surface, while being the highest-volume one.Root cause
getFileContentinsrc/review/grounding-wire.tskeys the cache on(repo, path, ref), whererefis the commit SHA. The comment there is correct that this is safe — a blob at an immutable commit never changes, so a hit is always reusable verbatim.But it is keyed on the wrong immutable thing. Grounding reads context files (type declarations, generated schemas, manifests), which are usually not the files the PR changed. Those files are byte-identical across commits — yet every push mints a new
ref, so every one of them misses again.The cache is therefore invalidated by commits that did not touch the file it is caching.
Evidence (Orb, 24h to 2026-07-31T12:35Z)
(repo, path)pairsWorst offenders — each row is one file, re-fetched from the GitHub Contents API this many times in a day:
packages/contract/index.d.tspublic/metagraph/openapi.jsonpublic/metagraph/api-index.jsonpublic/metagraph/types.d.tsapps/ui/src/routes/-subnets-netuid-page.tsx.release-please-manifest.jsonpackage.jsonapps/loopover-ui/public/openapi.jsonA generated
index.d.tsdid not change 31 times in a day. Each of those is a real Contents API call against the installation's REST quota — the same quota #10184 is separately trying to stop burning.Recommended fix: conditional requests
Store the response ETag alongside the cached content under a
(repo, path)key, and sendIf-None-Matchwhen theref-keyed lookup misses. GitHub answers 304 Not Modified for an unchanged file, and a 304 does not count against the REST rate limit. So:This keeps the existing
(repo, path, ref)fast path untouched for a true same-ref hit.A cheaper variant worth considering first, if ETag plumbing is too much: keep a
(repo, path, ref) -> blobShamapping plus a content-addressed(repo, path, blobSha) -> contentstore. That de-duplicates storage but still costs one call per new ref, so it does not save quota — the ETag route is the one that does.Instrumentation note
Whatever lands should emit a distinct outcome for "revalidated 304" rather than folding it into either
grounding_cache_hitorgrounding_cache_miss— otherwise this cache acquires a fourth vocabulary for reuse, which is the exact problem #10208 is about.Verification
All figures queried directly against the Orb Postgres primary. The repeat-read count is
total misses - distinct (repoFullName, path)fromaudit_eventswhereevent_type = 'github_app.grounding_cache_miss', which is a lower bound on the waste: it counts a file re-fetched N times as N-1 avoidable calls only if all N shared content, which the top rows above make near-certain but do not strictly prove.