Claude Code Auto Mode: Tune the Permission Classifier

Auto mode routes tool calls through a classifier that blocks anything irreversible, destructive, or aimed outside your environment — force pushes, deleting remote branches, curl piped into a shell, production deploys, and data leaving your trust boundary. By default the boundary is only your working directory and the current repo's configured remotes, so anything else counts as external until you describe it in autoMode.environment.
The classifier has no way to know that org is yours. It trusts the working repository and its configured remotes by default, and treats every other destination as potentially external. Add a line like your source control host and org to autoMode.environment in your user settings, then run claude auto-mode config to confirm the entry took effect.
No, and that is deliberate. The classifier reads autoMode only from user settings, managed settings, and an inline settings payload — never from .claude/settings.json or .claude/settings.local.json. Both live in the repo, so a checked-in file or a build step could otherwise inject allow rules and widen its own permissions.
You replace that entire built-in list. Omitting it from soft_deny discards every built-in destructive-action rule, including force push and curl piped into a shell; omitting it from hard_deny discards the data-exfiltration rule. Run claude auto-mode defaults to print the built-in rules before you take ownership of any list.
Add content-scoped ask rules such as a git push pattern and a gh pr create pattern to permissions.ask. Explicit ask rules are evaluated before the classifier and always force a prompt, so auto mode keeps handling everything else while those two actions still wait for you.

Key Takeaway
Claude Code auto mode routes every tool call through a classifier that blocks irreversible, destructive, or outward-facing actions instead of prompting you. By default it trusts only your working directory and the current repo's remotes, so routine internal operations get blocked until you describe your infrastructure in autoMode.environment as plain prose.
The first hour I ran auto mode it blocked a push to a repository I own, on a branch I created, in an organisation I had used every day for a year. Nothing was wrong. The classifier simply had no way to know that github.example.com/acme-corp was mine, so from where it sat the push was an upload to a host it had never been told about.
That is the whole mental model, and it is worth internalising before you start adding allow rules: the classifier is not judging the command, it is judging the destination against a boundary you have not drawn yet. This post covers where that boundary is defined, the precedence order inside the classifier, the CLI subcommands that let you see what is actually in effect, and the one configuration mistake that silently removes every built-in security rule.
The permissions system still runs first. Deny rules block before the classifier is ever consulted and cannot be overridden by it or by anything you say in conversation. Explicit ask rules also run first and always force a prompt. Only what survives both reaches the classifier, which then decides whether the action is safe enough to run unattended.
That ordering matters for anything that must never happen. If the requirement is genuinely absolute, express it as permissions.deny in managed settings — a classifier is a model making a judgement, and a policy boundary should not be a judgement call. Use the classifier for the enormous middle ground where the honest answer is that it depends on where the command is pointing.
Inside the classifier the precedence is fixed, and knowing it tells you which list to edit when something is wrong:
That last tier is narrower than people expect. Asking Claude to clean up the repo does not authorise a force push; asking it to force-push this branch does. A general request is not intent, which is why vague prompts get blocked and precise ones do not.
For most teams this is the only field you need. It tells the classifier which repos, buckets, hosts and registries are inside your boundary, and by extension what external means — anything not listed is a potential exfiltration target. The entries are prose, not regex and not tool patterns. Write them the way you would describe your infrastructure to a new engineer on their first day.
// ~/.claude/settings.json — the classifier does NOT read
// .claude/settings.json or .claude/settings.local.json, because
// both live in the repo and a build step could inject allow rules.
{
"autoMode": {
"environment": [
"$defaults",
"Organization: Acme. Primary use: software development",
"Source control: github.example.com/acme-corp and all repos under it",
"Trusted internal domains: *.corp.example.com, api.internal.example.com",
"Trusted cloud buckets: s3://acme-build-artifacts, gs://acme-ml-datasets",
"Internal package registry: npm installs route through artifacts.example.com",
"Sensitive remote targets: the prod-* Kubernetes namespaces",
"Protected IaC scopes: everything under infra/terraform/prod/"
]
}
}The literal string of dollar-sign defaults splices the built-in entries in at that position, so your custom lines sit before or after them and you keep inheriting updates as the built-in list changes across releases. A reasonable rollout is to start with the defaults, add your source-control org and key internal services — which clears the most common blocks immediately — then add domains and buckets, and fill in the rest as denials come up.
The classifier reads autoMode from user settings, from managed settings, and from an inline settings payload. It does not read it from .claude/settings.json or .claude/settings.local.json. Both of those live inside the repository, and a checked-in file or a build step could otherwise inject its own allow rules — a supply-chain hole that would let a cloned repo widen its own permissions. If you have an autoMode block in a local project file today, move it to your user settings; it stopped being read in v2.1.207.
Three ways to draw a boundary, from softest to hardest:
| Boundary | Mechanism | Behaviour in auto mode |
|---|---|---|
| One-off, this session only | Say it in conversation | Works until compaction drops the message that stated it |
| Prompt me before this action | permissions.ask | Always prompts; the classifier cannot auto-approve past it |
| Never, regardless of intent | permissions.deny in managed settings | Blocks before the classifier runs; nothing overrides it |
// A content-scoped ask rule is evaluated BEFORE the classifier
// and always prompts, even in auto mode. This is how you keep a
// human checkpoint on the two actions that leave your machine
// while auto mode handles everything else.
{
"permissions": {
"ask": [
"Bash(git push *)",
"Bash(gh pr create *)"
]
}
}Four subcommands turn this from guesswork into reading. The one I use most is config, because it prints the merged result rather than what you wrote — the difference between the two is where most confusion lives.
# Print the built-in rules as JSON — read these before you
# ever replace a list, because replacing one discards them.
claude auto-mode defaults
# Read one rule without piping through jq (v2.1.208+).
# Matching is a case-insensitive prefix on the rule label.
claude auto-mode defaults --label 'Git Destructive'
# What the classifier ACTUALLY uses: your settings merged over
# the defaults, with "$defaults" expanded in place.
claude auto-mode config
# Ask Claude to critique your custom rules — it flags entries
# that are ambiguous, redundant, or false-positive machines.
claude auto-mode critique
# Remove the autoMode block from ~/.claude/settings.json.
# Managed settings and --settings rules still apply.
claude auto-mode reset --yesSetting environment, allow, soft_deny or hard_deny without including the defaults marker replaces that entire built-in list. Drop it from soft_deny and you discard every built-in destructive-action rule, force push and curl-piped-to-shell included. Drop it from hard_deny and you discard the data-exfiltration rule. Only omit it when you have run the defaults subcommand, copied the rules out, and reviewed each one against your own pipeline.
By default a narrow shell allow rule stays in effect in auto mode and resolves before the classifier runs. Claude Code suspends only the broad rules that grant arbitrary execution, such as a wildcard Bash rule or a wildcarded interpreter. That leaves a real gap: a narrow rule can pass a destructive argument the rule's prefix never anticipated — a script path, an unexpected flag — straight through without the classifier ever seeing it.
Setting autoMode.classifyAllShell to true suspends every Bash and PowerShell allow rule while auto mode is active, so the classifier evaluates each shell command regardless of your allow list. The trade is latency for coverage: a command an allow rule would have approved instantly now waits for a classifier decision, and each one counts as a classifier call. It applies only while auto mode is active and your allow rules behave normally in every other mode.
Run the auto-mode-setup command rather than writing the environment block by hand. It reads this project's CLAUDE.md, README, config files and git remotes, plus the hosts and command names from your recent sessions in the project — never your messages — and drafts the entries. You accept or discard the draft as a whole, then edit single lines in user settings afterwards.
Open the permissions dialog and select the Recently denied tab; every classifier denial is recorded there, and pressing r marks one for retry so Claude is told it may run that call again. The reason shown is usually the fixed text about being blocked by the classifier rather than an explanation, because the classifier scores actions on an internal severity scale instead of writing prose. Pick the fix from what the call was reaching for:
Auto mode is worth the setup, but treat the environment block as documentation of your infrastructure rather than a list of workarounds. Every entry you add is a claim about what is inside your boundary, and the classifier will act on it for every session afterwards. Write the entries you would be comfortable defending in a review, keep the built-in rules spliced in, and put the things that must never happen in a deny rule where no judgement is involved at all.
Sources & further reading