Claude Code's Model Allowlist Was Approving Releases You Never Evaluated
availableModels matches version prefixes, so a model that shipped this morning can be permitted in a locked-down deployment nobody touched. Two new managed settings close that, and both fail open on any client older than 2.1.283.
An allowlist that grows on its own is not an allowlist.
Until two days ago, an organization running Claude Code under managed settings with availableModels set to ["claude-opus-5"] believed it had pinned a model. What it had written was closer to a subscription. Entries in that list match by version prefix, and a prefix matches any later model ID that extends it. The documentation says so in the plainest possible terms: claude-fable-5 permits both Fable 5 and Fable 5.1, while claude-fable-5-1 permits Fable 5.1 only. So the moment Claude Code learned how to talk to a newer release in a permitted family, that release was allowed, in a file no administrator had opened since the compliance review.
Claude Code 2.1.283, published to npm at 18:46 UTC on September 25, adds the two settings that close it. I want to spend most of this on why the old behavior lasted as long as it did, and on the one line in the new documentation that decides whether the fix actually holds.
Why prefix matching was a defensible decision and a compliance problem
Prefix matching exists because model IDs are ugly and versioned and nobody wants to edit a policy file every three weeks. Writing sonnet and getting whatever Sonnet is current is a real convenience, and for most teams it is the correct default. It keeps a fleet from stranding itself on a deprecated model because one person forgot a pull request.
The trouble is that "whatever is current" and "what we evaluated" are different sets, and an allowlist is the artifact you point at when someone asks which one you deployed. Every regulated team I have talked to treats the model roster as an evidence question: here is the list, here is the eval we ran against each entry, here is the sign-off. Prefix matching turned that list into a template rather than a record. The list said Opus 5. Production was running Opus 5.5 as soon as it existed, with no change to any file and no entry in any audit log that an administrator wrote.
My position: this was the single most underrated misconfiguration in enterprise agent deployments this year, and almost nobody knew they had it, because the setting behaves exactly as you would expect right up until a new version ships.
What the allowlist actually covers
Worth knowing how wide the surface is, because it is wider than the model picker. According to Claude Code's model configuration docs, availableModels applies to the main session model reached through /model, the --model flag, ANTHROPIC_MODEL, the model setting and ANTHROPIC_DEFAULT_MODEL; to alias resolution; to fast mode; to subagent and teammate models; to skills and commands; to the advisor model; and to the background agent model. That is close to every path by which a model gets chosen.
Enforcement differs by path, and the differences matter more than the coverage does:
/modelrejects a blocked switch with an error. The user sees it.- The
--modelflag,ANTHROPIC_MODEL, or themodelsetting get replaced at startup with a warning. The user probably sees it. - A subagent or teammate override runs on a fallback model rather than failing.
That third case is the one to sit with. A subagent configured for a model your policy blocks does not stop. It runs on something else, produces output, and the run looks successful. If your evaluation was model-specific, and subagent work is a meaningful share of what your agents do, you have results attributed to a model that did not produce them.
The two new settings, and the line that decides whether they hold
Both require Claude Code v2.1.283 or later.
deniedModels takes a list of models to block. A listed model is blocked even when availableModels permits it, and it works with no allowlist at all, which makes it the low-friction option for holding back one release. The docs' own example:
{
"availableModels": ["opus", "sonnet"],
"deniedModels": ["claude-opus-5-5"]
}
Anthropic describes that as permitting Opus and Sonnet while blocking Opus 5.5 "in every spelling, including dated and provider-specific IDs." I have not enumerated provider-specific IDs to test the claim, so take it as the vendor's description rather than something I verified.
availableModelsMatch set to "exact" is the structural fix. Each model ID in availableModels then permits only the version it names, and a newer version of a listed ID stays blocked until somebody adds it. This inverts the default from permit-on-release to deny-until-listed, which is the posture an evidence-based deployment needed all along.
Now the line that decides whether any of this is real. The docs give it in one sentence, about both new keys:
Earlier versions ignore both keys, so also set
requiredMinimumVersionto keep those versions from starting.
A control that older clients ignore is not a control. It is a comment. Any machine in your fleet running 2.1.282 reads your carefully written deny-list, drops it on the floor, and falls back to prefix matching, which is to say it permits the exact release you just blocked. Nothing warns you, because from that binary's point of view the key does not exist. So deniedModels and availableModelsMatch are not two settings. They are three, and the third one is requiredMinimumVersion.
There is one more wrinkle for anyone embedding Claude Code. An application that sets CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST takes its own model configuration over the managed model, fallbackModel, modelPicker, and modelOverrides keys, and over model-selection variables in a managed env block. A managed availableModels allowlist stays in force unless the host app supplies its own list. Read that carefully: the allowlist is the one thing that survives the host exception, which is precisely why it is worth making exact rather than leaving it as a prefix that the host's model choice can slip through.
Put this into practice
Five steps, cheapest first.
Find out what your current list permits today, not when you wrote it. Open your managed-settings.json or MDM policy, take each availableModels entry, and ask whether a newer release in that family exists. If the entry is a family name or a short prefix, the answer is the list is broader than your evaluation.
Deploy the three keys together or do not bother. availableModels for the allowlist, availableModelsMatch: "exact" for the semantics, requiredMinimumVersion so an old binary cannot ignore the first two. Shipping the first two without the third gives you the paperwork of a control and none of the enforcement.
Use deniedModels for the hold-back case specifically. If you are broadly happy with a family and want one release out until an eval finishes, name it. That is a smaller, more reversible change than flipping your whole deployment to exact matching on a Friday.
Go look at what your subagents are actually running. Because a blocked subagent model falls back rather than failing, this is the path where a policy violation looks like a normal run. Check your traces for the model recorded on subagent turns, not just on the main session.
Then check settings precedence, because it is the part people get wrong. Managed settings sit above the command line, project local settings, shared project settings, and user settings, in that order. Nothing a user sets overrides a managed key, apart from a short list of documented exceptions. If your policy is in a project file rather than a managed source, a user can replace it, and the audit trail you think you have is a suggestion.
Honest limitations
Exact matching hands you the upgrade calendar, and that bill comes due. Every new release is blocked until a human edits a file and ships it through whatever your MDM change process is. On a fleet with a slow policy pipeline the realistic outcome is engineers stuck two model generations behind, filing tickets, and eventually finding a machine without managed settings to do real work on. That failure mode is worse than the one you were fixing. If you turn this on, you need someone who owns the list and a path to update it in days rather than quarters.
The deniedModels "every spelling" behavior is the vendor's claim and I have not tested it against provider-specific IDs on Bedrock or Vertex. If you are multi-provider, verify it yourself before you rely on it.
None of this touches a model reached outside Claude Code. An engineer with an API key and a Python file is not covered by any of these keys, and neither is a model called through a gateway that Claude Code is not the client for. This is a harness-level control, which is exactly its value and exactly its ceiling.
And the ship date here comes from the npm publish timestamp on the @anthropic-ai/claude-code packument, because the changelog carries no dates at all. That is a small thing that makes version archaeology harder than it should be for anyone reconstructing when a control became available.
What to do with this
The interesting part of this release is not the two keys. It is what they admit: the model a deployment runs is a supply-chain fact, not a preference, and the harness is where that fact gets decided. Enforcement moved out of the model and into a JSON file your MDM pushes, and that is a better place for it, because you can read a JSON file.
Go read yours. If availableModels is a family name, you are running whatever shipped most recently, and you have been for a while. Whether that bothers you depends on what you promised, and only you know what you promised.
Sources: Claude Code CHANGELOG, Claude Code model configuration docs, settings files and precedence, settings reference, npm packument for @anthropic-ai/claude-code.
Medium metadata
- Title: Claude Code's Model Allowlist Was Approving Releases You Never Evaluated
- Subtitle:
availableModelsmatches version prefixes, so a model that shipped this morning can be permitted in a locked-down deployment nobody touched. Two new managed settings close that, and both fail open on any client older than 2.1.283. - Tags: Claude Code, AI Agents, DevOps, Security, Enterprise Software
- Canonical: import from the fervorai.dev URL