How I stopped my Claude Code subagents from secretly running on Fable instead of Sonnet | Thomas Witt: Tech Entrepreneur & Angel Investor
How I stopped my Claude Code subagents from secretly running on Fable instead of Sonnet
tech
Guess which layer stopped holding.
I run Claude Code with a big session model, Fable or Opus 5, plus a small zoo of subagents doing the boring parts. Gateway agents, formatters, checkers. The kind of mechanical stuff you pin to a small model once, in the agent’s frontmatter, and then never think about again.
At some point the token consumption stopped matching my gut feeling of what I’d actually been doing that week. Nothing was broken. Nothing errored. Everything worked. It just cost more than it should have.
So I decided to take a deeper look at how Claude Code actually picks the model for a subagent. It turned out the pin I’d been trusting sits in the one layer you shouldn’t trust.
Disclaimer: This is what I run on my own machine, on my own projects. Hooks can block your own dispatches, that’s the whole point of this one, so if you wire it up wrong you’ll be staring at a blocked Task call and wondering why. Also, precedence behaviour in Claude Code changes between releases. Verify against the version you’re on.
Why you want subagents in the first place
Before the complaining starts, let me be clear about one thing: subagents are good. This post is not an argument against them, it’s an argument for pinning them properly.
The reason to use one isn’t that it’s a smaller model. It’s that it runs in its own context window . It goes off, does something loud and messy, and hands back a short answer. The noise never lands in your main session. You get the three lines that matter instead of the four megabytes they came from.
Which means the ideal subagent job looks like this: fetch a lot, filter, return a little. And that job needs a model that is obedient, not brilliant. Something like Sonnet does it all day. Running it on Fable or Opus 5 is paying frontier prices for grep with good manners.
My list of agents that should never touch a frontier model:
AWS, especially CloudWatch Logs. A log query for one request ID returns an ocean. You want the stack trace and the timestamp. This one alone justifies the whole pattern.
GitHub. Issues, PR diffs, CI status, “which commit touched this file”. Long output, small answer.
Honeybadger. Error occurrences, backtraces, “is this the same bug as last Tuesday”. Structured input, structured output.
Langfuse. Trace dumps are enormous and 95% of every trace is irrelevant to the question you’re asking about it.
Static analysis: RubyCritic, RuboCop and friends. The report is a hundred pages, the actionable part is nine lines.
Test runners. You do not need Fable to look at RSpec output and tell you which four specs are red. You need something that can read.
Pinning one looks like this, in .claude/agents/cloudwatch-digger.md:
name: cloudwatch-digger<br>description: Queries CloudWatch Logs and returns only the relevant lines<br>model: sonnet<br>tools: Bash, Read
One line. model: sonnet. That’s the whole pin, and that’s exactly why it hurts when it silently stops working. The agents you bother to pin are, by definition, the ones you dispatch most often and look at least.
The first uncomfortable thing
Claude Code resolves which model a subagent runs on in this order:
rank<br>layer<br>how you set it
environment variable<br>shell / launch config
per-invocation parameter
model on the dispatch itself
agent frontmatter
model: in .claude/agents/.md
session model<br>whatever you started the session with
Four layers. And the one that everybody actually uses, the frontmatter pin, because it’s the one that’s documented, obvious and writable once, is rank 3 of 4.
That would be fine if rank 3 always held. It doesn’t. Across several releases the frontmatter layer has silently dropped out, and pinned agents fell straight through to rank 4: the session model. Which in my case is Fable.
So the cheap little agent you dispatch two hundred times a day quietly runs on the most expensive thing you have. And here’s the part I find genuinely annoying: there is no signal. No error, no warning, nothing in the transcript that looks different. The agent does its job. It just does it at a multiple of the price.
A crash is polite, it tells you. This doesn’t tell you anything. It shows up four weeks later as a number.
Recognising the pattern
I wasn’t the first one to run into this. There’s a whole class of upstream reports about frontmatter pins being ignored after an update, and the workaround people keep confirming is always the same: pass the model explicitly on the dispatch. That’s rank 2, one layer above frontmatter, and rank 2 has never been the layer that breaks.
Big shoutout to everyone who bothered to file those issues with reproductions. Silent cost regressions are exactly the kind of bug nobody files, because nobody notices.
Which leaves an obvious problem:...