PR Review Context Benchmark

Every claim about token reduction on this site used to rest on trace-mcp’s own internal estimators — the session analytics numbers, measured by the tool on itself. That is not good enough for anyone outside the project. This page is the measurement on somebody else’s code: 60 real merged pull requests across 6 open-source repositories, with the PR numbers and commit SHAs pinned in the repo so the run reproduces.

TL;DR

Assembling review context for a pull request with trace-mcp costs a median 72.7% fewer input tokens than loading the diff plus every file it touches. It makes more of the code the change can break visible — 22% of affected call sites readable against 20%, and 100% at least located — and less of the changed code itself: 71% of changed symbols arrive with their bodies, against 100% for whole files. That second number misses a preregistered bar.

The review written from it holds up. Sending both contexts to the same model on the same PRs, the trace-mcp arm understood the change as often as the naive one and asserted 0.80 problems that are not there per PR against 0.58 — both inside a preregistered bar written before the calls were made. An earlier run of that same arm published a 15 point comprehension loss; it was measured on a context that contained no source code and is struck. Both halves are below; neither number should be quoted without the other.

  naive file loading trace-mcp
input tokens, median 13595 3291
input tokens, p90 44246 7694
input tokens, worst case 70417 16060
cost per PR, median $0.0408 $0.0099
cost per PR, p90 $0.1327 $0.0231
changed symbols readable 100% 71%
affected call sites readable 20% 22%
affected call sites at least located 20% 100%

Measured at trace-mcp 3.23.2 (cb8ab30c) on 7 September 2026 — published as a result from that build, not as a claim about the current one. What this run set out to measure, the bar it had to clear and the verdict against that bar: preregistration.

Dollar figures are input tokens priced at claude-sonnet-4-5, $3 per million input tokens. Indexing a repository costs a median 224 ms per PR once the initial index exists, and is amortised across every query against that repo.

What was measured

The carrier task is AI code review of a real pull request — the most token-hungry production pipeline in the code-agent market, and the one where the entire cost is context assembly.

Two arms, same pull requests, same tokenizer (gpt-tokenizer, exact counts — not a characters-over-four estimate), same prompt skeleton:

Both contexts are assembled against the same commit — the PR head — because that is the state a review agent has in front of it.

Dataset

60 merged, bug-fix-titled pull requests from honojs/hono, axios/axios, expressjs/express, psf/requests, pallets/flask and sindresorhus/got — TypeScript, JavaScript and Python. Selection criteria, applied before any measurement:

A further 12 PRs were mined but excluded at run time because the diff touched no indexed symbol at all — documentation, lockfiles, CI config. Including them would have inflated the headline: the trace-mcp arm for such a PR is nothing but the diff, so the “saving” would be an artifact of there being no code to load.

Reproducing it

git clone https://github.com/nikolai-vysotskyi/trace-mcp && cd trace-mcp
pnpm install
npx tsx scripts/bench-pr-context.ts        # writes benchmarks/pr-context/results.json

The script clones each upstream repo into node_modules/.cache/pr-context/, checks out the pinned SHA, indexes it, and writes every per-PR row alongside the aggregates. Every number on this page is rendered from docs/_data/pr_context_bench.json, which that script generates — none of them is typed by hand.

Where trace-mcp did not pay off

A benchmark without this section is marketing. On this dataset 56 of 60 PRs were cases where the index barely earned its keep:

PR files changed symbols naive trace-mcp saved
got/pull/2379 2 3 2808 4589 -63.4%
axios/pull/11078 1 2 803 958 -19.3%
axios/pull/11109 3 6 3497 3887 -11.2%
axios/pull/11035 3 4 2764 3016 -9.1%
axios/pull/11039 2 2 1708 1861 -9.0%
axios/pull/11118 2 8 3551 3803 -7.1%
hono/pull/5239 3 4 6209 6571 -5.8%
express/pull/6274 1 2 1071 1103 -3.0%
express/pull/7029 1 2 3204 3277 -2.3%
express/pull/7159 1 2 3221 3294 -2.3%
axios/pull/11073 1 2 960 974 -1.5%
express/pull/6088 2 2 4478 4497 -0.4%
express/pull/7265 1 1 3514 3527 -0.4%
hono/pull/5250 2 1 5846 4446 23.9%
axios/pull/11094 6 14 10960 6772 38.2%
got/pull/2380 2 4 13964 8042 42.4%
axios/pull/11087 5 15 10748 6102 43.2%
axios/pull/11121 8 21 17965 10164 43.4%
got/pull/2458 4 8 13226 7308 44.7%
got/pull/2388 2 4 12598 6917 45.1%
hono/pull/5268 2 4 9572 5176 45.9%
axios/pull/11172 8 20 31348 16060 48.8%
requests/pull/6806 1 3 6017 3066 49.0%
requests/pull/7425 2 3 9160 3621 60.5%
got/pull/2391 1 3 9670 3523 63.6%
got/pull/2367 2 2 6604 1984 70.0%
requests/pull/7431 3 7 18827 5442 71.1%
got/pull/2399 3 5 11384 3287 71.1%
flask/pull/5620 10 27 44519 10960 75.4%
flask/pull/5808 1 3 8617 2070 76.0%
flask/pull/6096 4 10 32753 5627 82.8%
got/pull/2471 2 4 31642 5433 82.8%
requests/pull/7433 2 7 34845 5929 83.0%
hono/pull/5283 2 4 19517 3245 83.4%
got/pull/2470 2 6 32118 5176 83.9%
hono/pull/5244 2 2 18133 2808 84.5%
hono/pull/5282 2 2 9829 1503 84.7%
express/pull/4893 3 6 51767 7694 85.1%
hono/pull/5288 2 2 9886 1437 85.5%
hono/pull/5236 4 9 44246 6264 85.8%
hono/pull/5291 2 2 19866 2707 86.4%
hono/pull/5202 2 2 17078 2287 86.6%
requests/pull/6963 2 9 16057 2119 86.8%
hono/pull/5280 2 2 18190 2389 86.9%
hono/pull/5215 2 2 17101 2184 87.2%
flask/pull/5917 7 26 64258 8158 87.3%
axios/pull/11096 8 13 70417 8451 88.0%
got/pull/2454 3 5 55721 6130 89.0%
requests/pull/7502 2 8 34807 3151 90.9%
requests/pull/7395 2 6 30835 2487 91.9%
got/pull/2362 2 3 18777 889 95.3%
got/pull/2266 1 1 9655 344 96.4%
express/pull/7390 2 3 43097 1493 96.5%
express/pull/5555 3 3 46454 1579 96.6%
requests/pull/7371 1 4 25197 596 97.6%
axios/pull/11119 1 2 24348 491 98.0%

They share a shape: a small change to one or two small files. When the whole file is 200 lines, loading it outright is already cheap, and the symbol bodies plus the callers and the impact list come to more than the files themselves — which is why 13 of these rows are negative, not merely small. got#2379 is the extreme at -63.4%: the assembled context costs more than twice what reading both files would. If your repository is small, or your PRs touch only small files, this index does not solve a problem you have. The saving scales with how much of a file a reviewer does not need.

Two further limits worth stating plainly:

Does the thinner context produce a worse review?

That question used to live in a “what this does not measure” section. It is measured now, and the answer is no, within the bar that was set for it: on the same pull requests, the review written from trace-mcp’s context understood the change as often as the review written from the full files (67% against 65% — the sign is in our favour, which 60 pull requests cannot make significant, so read it as parity), and asserted +0.22 more false positives per PR. The preregistered bar — no more than a 10 point drop in comprehension and no more than +0.5 false positives per PR — was written before the calls were made, and this run meets both.

This is the second run under that preregistration. The first published a 15 point loss and is struck: its trace arm carried no source code, because get_context_bundle read symbol bodies through a bare require('node:fs') that throws under ESM into a silent catch. The bars did not move, the corpus did not move, and the diagnosis is published alongside. The naive arm scored the same 65% in both runs, which is the control the correction rests on.

The method: the same 60 pull requests and the same two assembled contexts — byte-for-byte the prompts counted above, dumped to disk by the token run — each sent to claude-sonnet-4-5 at identical settings, with the two resulting reviews handed to a judge together with the PR’s own diff as ground truth, blind and in randomised order.

  naive file loading trace-mcp
understood the change 65% 67%
false positives per PR 0.58 0.80
findings claimed per PR 3.2 3.5
review latency, median 93.0 s 92.9 s

Per-PR agreement: both arms understood the change on 36 PRs, only the naive arm on 3, only trace-mcp on 4, neither on 17.

Latency is level: 92.9 s against 93.0 s. The saving is in input tokens, not in wall clock — the struck run’s 17% latency win was the speed of a context with the code taken out of it.

So the honest reading is: trace-mcp’s review context costs about 30% of the tokens and the review written from it is not measurably worse on this corpus. What that does not say is that it is better, or that it holds on harder pull requests — the corpus is merged bug-fix PRs where the defect is visible in the diff, which flatters both arms (see below). Every review and every judgement is committed in benchmarks/pr-context/quality.json, so the scoring is auditable rather than asserted.

npx tsx scripts/bench-pr-context.ts --dump-prompts benchmarks/pr-context/prompts
npx tsx scripts/bench-pr-quality.ts        # writes benchmarks/pr-context/quality.json

What this still does not measure

For the levers that produce the token difference — presets, compact schemas and the TOON encoding — see cutting Claude Code token usage.

See also

Last updated: September 7, 2026