TL;DR
Agent(model:opus)indenyonly blocks the calls where Claude asks for Opus. If you want no subagent on Opus, pin their model withCLAUDE_CODE_SUBAGENT_MODELandCLAUDE_CODE_SUBAGENT_MODEL_FORCE=1, and keep the rule for what it actually stops.
Since v2.1.178, deny and ask rules can match a tool parameter with Tool(param:value). The docs' example is Agent(model:opus), and the obvious reading is "my subagents won't burn Opus anymore". I tried it in a repo with the rule in place, and that's not what happens: it stops one of the three ways a subagent ends up on Opus.
{
"permissions": {
"deny": ["Agent(model:opus)"]
}
}
The three ways, tested
With that rule in .claude/settings.json, I ran one claude -p session per case and checked which model the subagent ran on:
| How the subagent gets to Opus | Does the rule stop it? | What happened |
|---|---|---|
Claude passes model: "opus" when it starts it |
Yes | It never started |
Its definition in .claude/agents/ says model: opus |
No | Ran on claude-opus-5-5 |
| No model of its own, and your main session is on Opus | No | Inherited claude-opus-5-5 |
In the first case Claude gets this back, and in my run it didn't retry:
Permission to use Agent with model:opus has been denied.
The other two get through because the rule checks the parameter Claude sends in the call, and in neither of them does Claude send model. The docs put it plainly: a parameter that isn't sent never matches, not even Agent(model:*). And the third case is the everyday one: if you work on Opus, every subagent without a model of its own runs on Opus.
If you want no subagent on Opus
Pin the model in the env block of your settings.json:
{
"env": {
"CLAUDE_CODE_SUBAGENT_MODEL": "sonnet",
"CLAUDE_CODE_SUBAGENT_MODEL_FORCE": "1"
}
}
With both set, Claude Code ignores the model in agent definitions and Claude can't pass one when it starts a subagent. I checked it with haiku: the same subagent with model: opus in its definition ran on claude-haiku-5-5. Without FORCE, the variable is only a default and loses to the other two; the full order is in effort per model. Forks and skills with model: inherit stay on your session's model.
What the rule is good for
Letting your own definitions pick their model while Claude can't move up to Opus on its own. Put it under ask instead of deny and you get asked each time instead of blocked:
{
"permissions": {
"ask": ["Agent(model:opus)"]
}
}
Three details of the syntax:
- It compares the literal text.
Agent(model:opus)matches theopusalias, not a full ID such asclaude-opus-5-5.Agent(model:*opus*)covers both. - One parameter per rule. For
modelandisolation, write two rules. - Only
denyandask. Anallowrule takes no parameters, because allowing one value doesn't make the whole call safe.
The same syntax works for other top-level parameters, such as Agent(isolation:worktree) or Bash(run_in_background:true). It doesn't work on a tool's main field, such as Bash's command: for that, the usual rules like Bash(rm *) still apply. If one of your rules doesn't stop what you expected, start with --verbose to see the exact parameters of each call.
Official docs: Match by input parameter · Choose a model
Requirements
- Claude Code v2.1.178 or later for the rule, and v2.1.257 or later for
CLAUDE_CODE_SUBAGENT_MODEL_FORCE. Tested on v2.1.293 on October 13, 2026.