Skillforge Field notes on shipping with AI tools

Claude Code Auto Mode: A Configuration Guide

claude-codepermissions

Auto mode is now the built-in starting permission mode on Pro, Max, and Team plans, which means most people are using it without having configured it. It is not "approve everything". It is a second model, called the classifier, that reviews each action in place of you, with its own rule lists and its own idea of what counts as inside your trust boundary. When it says Blocked by classifier and Claude quietly tries something else, that is the classifier's default boundary, not a bug.

This is the configuration guide: the decision order an action goes through, what the defaults block, the four autoMode fields, and the two footguns that bite people who edit them. Everything here is checked against the Claude Code docs at code.claude.com (Permissions, Permission modes, Configure auto mode) on August 25, 2026. Version numbers are quoted where the docs pin behavior to a release, because this area has changed nearly every week.

The short answer

Four things explain almost every surprise:

  1. Your deny and ask rules run before the classifier. The classifier cannot override them.
  2. The classifier trusts your working directory and the remotes configured when the session started. Everything else is external until you say otherwise.
  3. You teach it your infrastructure in prose, in autoMode.environment, in ~/.claude/settings.json. Not in the repo.
  4. If it blocks 3 times in a row or 20 times in a session, auto mode pauses and prompting comes back.

The decision order

Per the docs, each action goes through a fixed order and the first matching step wins:

  1. Actions matching your allow, ask, or deny rules resolve immediately. Two carve-outs: writes to protected paths go to the classifier even when an allow rule matches, and from v2.1.218 so do rm and rmdir removals targeting a critical path. Ask rules that match on command content, like Bash(git push *), fall back to a normal permission prompt.
  2. Read-only actions and file edits in your working directory are auto-approved, except writes to protected paths.
  3. Everything else goes to the classifier.
  4. If the classifier blocks, Claude gets the reason and tries an alternative.

Step 1 is the important one. permissions.deny in managed settings is the only mechanism the docs describe as unoverridable by either the classifier or stated user intent. If a boundary has to hold, it goes there, not in prose.

There is also a rule-dropping behavior worth knowing before you trust your allow list. On entering auto mode, Claude Code drops broad allow rules that grant arbitrary code execution: blanket Bash(*) or PowerShell(*), wildcarded interpreters like Bash(python*), package-manager run commands, Agent rules, and (from v2.1.236) Monitor rules, since Monitor commands run through the shell. Narrow rules like Bash(npm test) survive, and the dropped rules come back when you leave auto mode.

That survival is itself a gap: a narrow prefix rule can pass a destructive argument the prefix never anticipated, and the classifier never sees it. If you want full coverage, route every shell command through the classifier:

{
  "autoMode": {
    "classifyAllShell": true
  }
}

This costs latency (each shell command now waits on a classifier call, and each one counts as a call) and applies only while auto mode is active. It needs v2.1.193 or later; earlier versions ignore the key.

What the classifier sees, and what it does not

The classifier reads your messages, tool calls other than read-only lookups, and your CLAUDE.md. Tool results are stripped, so hostile text in a file or a fetched web page cannot address it directly. A separate server-side probe scans incoming tool results for suspicious content before Claude reads them.

Two consequences follow from "tool results are stripped", and both explain real denials:

  • A recursive forced delete whose target is a shell variable that was never assigned in the visible conversation is blocked from v2.1.205, because the value came only from earlier command output the classifier never received. It clears when the literal path is named or written into the command.
  • Repository visibility has to be something the classifier can read. Your own message saying the repo is public counts. The output of gh repo view does not.

If you need to hand the classifier context about a result, a PostToolUse hook can annotate the call with a classifierContext field, which it reads as application-provided context.

What the defaults block

Trusted by default: local file operations in your working directory, installing dependencies declared in your lock files or manifests, reading .env and sending those credentials to their matching API, read-only HTTP, and pushing to any branch of the repository you are working in (including the default branch, since v2.1.211).

Blocked by default, in the categories most likely to hit a normal repo:

  • Downloading and executing code, curl | bash style
  • Force push, remote branch or tag deletion, remote history rewrites
  • git commit --amend on a commit HEAD did not create this session, or (from v2.1.198) one already pushed. A message-only reword of the session's own commit is not blocked
  • git reset --hard, git checkout -- ., git restore ., git clean -fd, git stash drop, git stash clear, all presumed to discard uncommitted work
  • Production deploys and migrations, terraform destroy and its Pulumi, CDK, and Terragrunt equivalents
  • Committing or pushing a change that would send secrets outside the repository when the pipeline runs it, which applies on any branch and even on public repos
  • Opening a pull request against a different repository or organization, forking, or pushing to a third-party repo, unless you named that target (v2.1.200+)
  • Changing where pushes go with git remote set-url or git remote add, unless you named the new remote (v2.1.200+)
  • Writing to Claude Code's own session transcripts under ~/.claude/projects/ (v2.1.205+). Reading them is fine

Before a command that would discard uncommitted work, Claude Code runs git status itself and shows the classifier whether staged, modified, or untracked work exists. It reports untracked files even when the repo sets status.showUntrackedFiles=no.

There is a nice property in the push rule that is easy to miss: a non-default branch whose name marks it as a deploy target, like production or gh-pages, is not covered by the permissive push default. The classifier judges that push on its own terms.

Teaching it your infrastructure

autoMode.environment is the field to reach for first. Entries are prose, not patterns: the classifier reads them as natural-language rules. The docs' own guidance is to write them the way you would describe your infrastructure to a new engineer.

{
  "autoMode": {
    "environment": [
      "$defaults",
      "Source control: github.example.com/acme-corp and all repos under it",
      "Trusted cloud buckets: s3://acme-build-artifacts, gs://acme-ml-datasets",
      "Trusted internal domains: *.corp.example.com, api.internal.example.com",
      "Key internal services: Jenkins at ci.example.com, Artifactory at artifacts.example.com"
    ]
  }
}

Three more fields replace the classifier's built-in rule lists, and inside the classifier they resolve in four tiers: hard_deny blocks unconditionally, soft_deny blocks next, allow acts as an exception to matching soft_deny rules, and then explicit user intent overrides the remaining soft blocks. The intent bar is specific: "clean up the repo" does not authorize a force push, "force-push this branch" does.

Footgun one: "$defaults" is not implied. Setting environment, allow, soft_deny, or hard_deny without the literal string "$defaults" in the array replaces that whole default list. Drop it from soft_deny and you discard every built-in soft block including force push, curl | bash, production deploys, and auto-mode bypass. Drop it from hard_deny and you discard the built-in data exfiltration rule. Each section is evaluated independently, so setting environment alone leaves the other three intact.

Footgun two: the classifier does not read project settings. It reads autoMode from ~/.claude/settings.json, from managed settings, and from inline --settings JSON. Not from .claude/settings.json and, since v2.1.207, not from .claude/settings.local.json. Both live in the repo directory, so a checked-in file or a build step could otherwise inject its own allow rules. The same trap catches defaultMode: "auto": put it in project settings and your session starts in Manual with no error.

Two inspection commands close the loop. claude auto-mode defaults prints the built-in environment, allow, soft_deny, and hard_deny lists as JSON, and claude auto-mode config prints what the classifier actually uses with your settings applied and "$defaults" expanded in place. Run the second one after every edit. claude auto-mode critique asks for feedback on custom rules, and claude auto-mode reset (v2.1.212+) removes the autoMode section from user settings.

Keeping a human checkpoint

Auto mode plus one checkpoint is a common shape, and the mechanism is permissions.ask. Content-scoped ask rules are evaluated before the classifier and always force a prompt, because an explicit ask rule is your stated intent to be asked:

{
  "permissions": {
    "ask": [
      "Bash(git push *)",
      "Bash(gh pr create *)"
    ]
  }
}

Saying "don't push until I review" in conversation also works: the classifier treats a stated boundary as a block signal, and it holds until you lift it, with Claude's own judgment that the condition was met not counting as lifting it. But boundaries are not stored as rules. The classifier re-reads them from the transcript each check, so context compaction can drop the message that stated one. For a durable guarantee, use a rule.

The fallback thresholds

If the classifier blocks an action 3 times in a row or 20 times in a session, auto mode pauses and Claude Code resumes prompting; approving the prompted action resumes auto mode. The thresholds are not configurable. Any allowed action resets the consecutive counter, while the total counter persists for the session.

This is the number that matters most to us, because we run Claude Code unattended on a schedule. In a non-interactive -p run with no --permission-prompt-tool, there is no prompt to fall back to: once repeated blocks hit a threshold, the action does not run and Claude keeps working, in the main conversation and in its subagents alike. The run does not stop. So an unattended session with a misconfigured environment does not fail loudly, it quietly gets less done. Configure environment before you schedule anything, and check /permissions under the Recently denied tab afterwards.

Subagents get checked at three points, incidentally: the delegated task description before spawn (v2.1.178+), each of their actions with the parent's rules and any permissionMode in their frontmatter ignored, and their full action history on return, which can prepend a security warning to the results.

FAQ

How do I turn auto mode on or off?

Cycle modes with Shift+Tab in the CLI, the mode indicator in VS Code, or the mode selector in Desktop. To make it the starting mode, set "permissions": {"defaultMode": "auto"} in user or managed settings, not project settings. Administrators can remove it entirely by setting permissions.disableAutoMode to "disable" in managed settings, which also makes --permission-mode auto start in Manual instead.

Why does Claude Code say auto mode is unavailable?

The docs describe that as an unmet requirement rather than an outage, and the requirement is usually the model. On the Anthropic API and Claude Platform on AWS, auto mode needs Opus 4.6 or later, Sonnet 4.6 or later, or Fable 5. On Bedrock, Google Cloud's Agent Platform, Microsoft Foundry, and gateway sessions, only Sonnet 5, Opus 4.7 or later, and Fable 5. Sonnet 4.5, Opus 4.5, Haiku, and claude-3 models are not supported anywhere. A different message naming a model and saying auto mode "cannot determine the safety" of an action is a failed classifier request, which is usually transient.

What does Blocked by classifier actually tell me?

In most sessions, nothing beyond the fact of the block: from v2.1.208 that string is fixed text, because the classifier scores actions on an internal severity scale rather than writing explanations. Some sessions run a classifier model that writes a short explanation instead. Which one you get is not configurable. The useful move is to look at the blocked tool call, which is shown in the transcript, the notification, and the Recently denied tab, and pick a fix from what it was reaching for: a destination Claude needs throughout the task goes in autoMode.environment, a command you want unreviewed from now on gets an allow rule, and a genuine one-off gets stated as intent in your next message so Claude can retry.

Is auto mode a safety guarantee?

No, and the docs say so directly: it reduces prompts but does not guarantee safety. Treat it as the right default for tasks whose general direction you trust, with permissions.deny in managed settings for the boundaries that have to hold and permissions.ask for the ones you want to see.