TLDR:
~/.claude/settings.jsondoesn't just set your interactive model — it leaks into every backgroundclaude -psubprocess (headless Claude call) that doesn't explicitly override it. I found this out the expensive way.
The Numbers That Didn't Add Up
I was watching my Max plan usage at 23% — after only a few hours.
That's not supposed to happen.
So I started digging. Pulled up the 7-day usage breakdown by model. Sonnet was at 1%. Which meant my RAG chunker wasn't the culprit — it hardcodes CHUNKER_MODEL = "claude-sonnet-4-6" and I knew it was firing.
The burning was somewhere else.
What I Found
Every claude -p (headless subprocess) my daemon scripts were firing… was quietly defaulting to Opus.
Not because I told it to. Because ~/.claude/settings.json had "model": "opus" and I hadn't thought about what that means for background processes.
The answer is: it means everything. Any subprocess that doesn't pass --model explicitly inherits your global default. And at the ~33K-token context floor every headless call loads by default, each Opus call carries a significant cost.
Per. Call.
Compare that to Sonnet or Haiku running at a fraction of the cost. Same floor, wildly different price tags.
The "Fix" That Backfired
My first instinct? Flip the default.
I changed settings.json from "model": "opus" to "sonnet" and thought I was done.
I was done for about 30 minutes.
"Go back to opus, I don't want to deal with that small of a context window."
Yeah. Fair. Sonnet is 200K tokens. Opus is 1M. That's a 5× difference in headroom on multi-file work — which is basically everything I use the interactive Claude session for. The cost savings on daemons weren't worth losing that.
Flipping the global default was the wrong lever.
The Actual Fix
The insight is that your interactive default and your daemon model should NEVER be coupled.
settings.json exists for you, sitting at a terminal. It should reflect what makes sense for that use case — which for me is Opus, full context window, no compromise.
Daemon subprocesses are a completely different thing. They should set their own model at the call site, every time, no exceptions.
The audit command that should be in every builder's rotation:
grep -r "claude -p" ~/Developer/ | grep -v "model "
Any subprocess invocation that comes back without --model in it is a silent burner. Fix each one individually — --model claude-sonnet-4-6 for most background tasks, or even Haiku if the work is simple.
If you're using the Python Claude Agent SDK instead of shell subprocesses, same rule applies:
ClaudeAgentOptions(model="claude-sonnet-4-6")
Never let it fall through to a default you didn't explicitly set in that process.
Why This Rule Got More Urgent
When I caught this on 2026-05-19, an Opus-defaulting daemon was annoying — it ate subscription quota I'd rather have spent elsewhere.
After Anthropic's June 15, 2026 billing split, the stakes changed. Everything headless — claude -p, Agent SDK calls, background hooks — moves off the flat Claude Max subscription and onto a metered Agent SDK credit pool at full API rates.
An Opus-inheriting daemon now isn't eating quota. It's putting real dollars on the card.
The invisible coupling I let slide before is now a billing hazard.
What I'd Tell a Builder Starting Today
Never rely on the user default for a non-interactive subprocess. Make the model explicit at every call site. Treat settings.json as an interactive-session preference, not a global policy.
The thing that bit me wasn't a bug in Claude or in my daemon code. It was an assumption I never even knew I was making — that a setting scoped to "my preferences" would somehow only apply to the things that felt like me.
It doesn't. It applies to everything. And everything has a price.
P.S. The grep one-liner above found three daemons I hadn't audited. Run it before June 15.