Editorial graphic headlined 'Spotify's 90%', with three figures measured across 761 Claude Code sessions: 9.7% of reads clear the 350-line threshold untargeted, 36.3% of all lines read sit inside those reads, and a 32.6% ceiling on read-token savings

Spotify's 90% Claude Code saving: what your repo gets


Key takeaways

  • The 90% is a per-scenario average, not a bill. Spotify’s shunt README reports 82–94% savings across three bulk-read scenarios on a 162,000-line Java monorepo. The most ordinary case — one large file — saves 82%.
  • Across 761 real Claude Code sessions we measured on September 11, 2026, only 9.7% of the 1,213 reads we could measure would trigger the hook at all. 89.2% came back under the 350-line threshold and would pass through untouched.
  • Weighted by size, those reads hold 36.3% of the lines we could measure. That puts the ceiling on read-token savings near a third. We counted lines rather than tokens, and it’s a ceiling before the output side of the bill, which every current Claude model prices at 5× input.
  • You can’t run shunt without Spotify’s Portal, though its eval suite runs without one. We cloned the repo and got 51 of 51 passing, then found a gap the suite misses: a count flag like head -n 5000 walks straight past the hook, however large the number.
  • Claude Code already returns subagent summaries instead of file contents. But as of v2.1.198 those subagents run on your model, so the context saving is free and the price saving isn’t.

What shunt actually blocks

Shunt is a Claude Code plugin that stops the expensive model from reading big files, then hands the reading to a cheap one. The plugin landed in Spotify’s marketplace repo on August 14, 2026; the write-up followed in September, reporting a 90% cut in token usage, and drew a long Hacker News thread.

The enforcement is the interesting part. Two PreToolUse hooks fire before the tool call happens: check-file-size blocks any Read on a file over 350 lines (SHUNT_MIN_LINES), and check-bash-read catches cat, head, tail, less and more doing the same job in a shell. Targeted reads pass: an offset, a limit, a pipe into grep. The blocked read comes back as an instruction to call the bulk-reader skill instead. That skill ships the files to a worker model and returns an answer rather than the file. Gemini 2.5 Flash is the model in the modes Spotify published, though the mode’s model field takes anything your Portal instance offers.

The first version of this was a block of rules in CLAUDE.md, and Spotify is candid about why that failed: the model could read the rules and ignore them, and every project needed its own copy. A hook doesn’t ask.

One half of the pitch is weaker than the other, and the README says so: “No enforcement for code-writer.” Only bulk reading is gated. Boilerplate generation depends on Claude remembering that the skill exists.

The plugin ships an eval suite that needs no Portal access, so we cloned the repo and ran it. All 51 tests pass — 17 for the Read hook, 17 for the Bash hook, 17 for the transport layer. For a plugin published as a blog-post companion, that’s a better-tested artifact than we expected.

Running the Bash hook by hand turned up a case the suite doesn’t cover. Against a 4,000-line fixture, cat, head, tail, less and more are all blocked, exactly as advertised. Add a count flag and the hook stops looking: head -n 5000, tail -n 3000 and head -c 200000 on that same file are all allowed through. The number itself is never checked, so a “targeted” read can be larger than the file that triggered the block.

Spotify half-knows. One of their own test cases is named “Parser bug: -n and 5 are separate args, 5 is treated as file path” — the suite covers head -n 5, which passes because the parser mistakes the 5 for a filename that doesn’t exist. It’s the right answer by the wrong route, and nobody wrote the test for -n 5000. In fairness, this is an efficiency gate rather than a security boundary, and an agent has little reason to reach for head -n 5000 on its own. But a hook that can be switched off by a flag it doesn’t read is a softer guarantee than “the agent does not get a vote.”

What the 90% is measured against

The 90% is the mean of three bulk-read scenarios, all on the same 162,000-line Java monorepo, and the README prints the full table rather than just the headline.

ScenarioLinesWithout shuntWith shuntSaving
Single large file4,01433,684 tokens5,737 tokens82%
Source + test pair7,40875,990 tokens4,148 tokens94%
Multi-file cross-service1,28116,221 tokens821 tokens94%
Code-write3,66740,614 tokens + generation833 lines to disk

The fourth scenario has no percentage at all, just a dash. The three that do are files of 1,281 to 7,408 lines: every one clears the 350-line threshold by a factor of four to twenty. And the single-large-file case, far and away the most common shape of read, is the weakest of the three at 82%.

Spotify is straight about the boundaries, too. You can’t delegate editing, because the worker’s summaries don’t carry reliable line numbers. You can’t delegate reasoning either: in their own testing the cheap model found surface patterns but missed a thread-safety bug. Both limits matter for the arithmetic below, because a read that precedes an edit has to be targeted anyway.

None of that makes the number wrong. It makes it a measurement of what happens when the hook fires. What happens to your bill is a different question.

How many of your reads would even qualify

We counted. Claude Code writes a transcript of every session to ~/.claude/projects, including what each Read returned. So we parsed all 761 sessions on this machine, spanning two Astro content sites, a Flutter app and a React Native app, and measured the line count of every read result against shunt’s own rule.

Of 5,131 Read calls, 1,213 came back as measurable file content. Within those:

  • 9.7% (118) were over 350 lines and not already targeted — the reads shunt would block.
  • 1.1% (13) were over the threshold but already carried an offset or limit, so the hook lets them through.
  • 89.2% (1,082) were under the threshold entirely.

That 89.2% was the number that surprised us. The mental image of an agent hoovering up enormous files is mostly wrong, at least in these repos: the agent reads a lot, but it reads small.

A long row of thin paper documents laid flat across a pale wooden desk, with only three thick terracotta-bound volumes standing tall among them

A fair objection is that the big reads are big, so counting calls understates them. That’s right, and it’s the number that decides the answer. Those 118 reads carried 71,697 of the 197,689 lines in the measured set — 36.3%. Apply shunt’s own 90% saving to exactly those lines and the ceiling on read-token savings is 32.6%.

A ceiling, not an estimate. It ignores the tokens spent describing each delegation, the 10–30 seconds of latency per round trip, and above all the output side of the bill. A commenter on the Hacker News thread put that last one well: “saving 90% of input tokens != saving 90% “of tokens”, output is wildly more expensive.” The pricing page bears it out at 5× for every current Claude model.

The same commenter had counted their own sessions on Opus 4.8 and found “only 5.9% of unique reads even qualify for the bulk-read operation.” That’s the number worth putting next to ours: 5.9% against our 9.7%, two people counting their own transcripts and landing four points apart. Their headline figure of 5–7% savings is a different measurement from our 32.6% — theirs runs against total session tokens, ours against read tokens only. One is roughly what’s left of the other once the rest of the bill enters the denominator.

The shell side looks similar. Of 1,832 commands shaped like a file read, 990 were piped or redirected and 189 carried a count flag — all of which the hook waves through. That leaves 653 candidates, and only the ones touching a file over 350 lines would actually be blocked.

Where this measurement is weak, since it’s ours: it covers 23.6% of Read calls, because the rest didn’t come back as numbered file content and we’d rather drop them than guess. The transcripts also span months and many Claude Code versions, not one pinned build. And it’s one team’s repos, none of them a 162,000-line Java monorepo — precisely the shape where shunt should look best. If you work in one, your number is higher than ours. That’s Spotify’s point, and this measurement doesn’t dent it.

One direction we can’t call: whether big files carry more tokens per line than small ones. Generated code and data tables are dense, long import blocks aren’t. If the big files are denser, 36.3% understates the token share and the real ceiling sits above a third.

What Claude Code already does without any of this

Much of the Hacker News thread argued that Claude Code already delegates reading to a cheap model, so shunt was rebuilding the furniture. Half of that is right, and the half that isn’t has a version number on it.

Subagents do keep bulk reading out of your main context. The docs describe using one when a task “would flood your main conversation with search results, logs, or file contents you won’t reference again” — the subagent works in its own context and returns only the summary. That’s the same context saving shunt’s bulk-reader delivers, and it’s been there all along.

What changed is which model does the reading: “As of v2.1.198, Explore inherits the main conversation’s model instead of always running on Haiku.” So the commenters insisting Claude Code routes reads to Haiku were describing a real behaviour — just not the current one. If you’re on Opus, Explore now reads your files on Opus.

That splits the thing shunt sells into two halves. The context saving you already have for free. The price saving you don’t, because your cheap reader isn’t cheap any more.

If you want this without Portal

A closed brass turnstile with a card reader on the left of a sunlit lobby, and an open doorway with no barrier just beyond it on the right

You can’t run shunt itself. Its prerequisites are jq, the portal plugin that supplies the Portal CLI, and an authenticated instance with the bulk-reader and code-writer modes; every delegation is one aika:invoke-chat against that instance. You can swap the worker model, since it’s a field on the mode, but only for something your Portal instance already runs. There’s no local mode and no way around Portal. For anyone outside Spotify the plugin is a design to read, not a tool to install, and we weren’t able to run it for this piece.

The cheap half is two environment variables, and the docs are blunt about why one isn’t enough: “Setting CLAUDE_CODE_SUBAGENT_MODEL by itself doesn’t change the model the built-in Explore and Plan subagents run on.” Explore is the one doing the reading. You need CLAUDE_CODE_SUBAGENT_MODEL set to haiku and CLAUDE_CODE_SUBAGENT_MODEL_FORCE set to 1, in the env block of a settings file, on v2.1.257 or later. That gets back the arrangement people assumed they already had: reading on Haiku, thinking on whatever you’re paying for. Add a PreToolUse hook that denies oversized reads and you’ve rebuilt shunt’s enforcement layer on machinery you already run.

Note what the second variable does, though. It forces every subagent onto that model — not just the ones doing bulk reading. The DIY path is messier than the one-liner it looks like.

Whether that’s worth doing is a genuinely open question for us, and our own numbers are what make it open. A third off read tokens is real money on a large enough bill. It’s also a lot of plumbing to defend a saving that lands well short of the headline, on a threshold we’d have to tune per repo: 5.5% of files clear 350 lines in one of our content sites, 21.7% in our Flutter app. The other levers on an LLM bill are cheaper to reach for, and we haven’t decided.

What we’d push back on is adopting the number without checking. The transcripts are already on your disk and the rule is one line in a README. Count your own reads before you plumb anything.

If you’re working through where the rest of your tokens go, we measured what Claude Code sends before you type a word and took the same look at whether Cursor Router actually saves money. More in our hands-on guides.

  • claude-code
  • token cost
  • hooks
  • model-routing
  • ai-coding-assistants

Frequently asked questions

Can I use Spotify's shunt plugin without Portal?

No. The plugin's README lists the Portal CLI as a prerequisite and routes every delegation through one aika:invoke-chat call against a Portal instance with AiKA enabled. You can pick the worker model, since the mode has a model field, but only from what your Portal instance already runs. If you don't run Portal, the closest equivalent is moving Claude Code's own subagents onto a cheaper model. That takes two environment variables: CLAUDE_CODE_SUBAGENT_MODEL and CLAUDE_CODE_SUBAGENT_MODEL_FORCE set to 1, on v2.1.257 or later.

Does the 90% figure mean my Claude Code bill drops 90%?

No. The 90% is the mean of three bulk-read scenarios on a 162,000-line Java monorepo, measuring only the tokens of the reads that were delegated. In our measurement of 761 real sessions, reads that shunt would intercept held 36.3% of the lines we could measure. That caps the saving on read tokens at roughly a third, and only before you account for output tokens: every current Claude model bills those at five times the input rate.

Doesn't Claude Code already send file reads to a cheaper model?

It used to. The docs state that as of v2.1.198, the Explore subagent inherits the main conversation's model instead of always running on Haiku. Subagents still return only a summary to the main conversation, so you keep the context saving, but the subagent now runs on whatever model you picked, so there's no price saving unless you move it yourself. That takes two environment variables, not one: the docs say setting CLAUDE_CODE_SUBAGENT_MODEL alone doesn't change the model Explore and Plan run on, and CLAUDE_CODE_SUBAGENT_MODEL_FORCE set to 1 (v2.1.257 or later) is what forces it.

What does shunt's 350-line threshold actually catch?

In our 1,213 measured reads, 89.2% returned fewer than 350 lines and would pass straight through the hook untouched. Only 9.7% were both over the threshold and not already targeted with offset or limit. Across four of our repos, the share of source files over 350 lines ranged from 5.5% on a content site to 21.7% on a Flutter app.