To start Claude Code in auto mode, run claude --permission-mode auto. This build’s claude --help lists the values the flag takes as (choices: "acceptEdits", "auto", "bypassPermissions", "manual", "dontAsk", "plan"). Inside a session you can switch instead. The permission modes page says: “press Shift+Tab to cycle permission modes. From auto, the first press switches to default, and the cycle then runs default → acceptEdits → plan.” To make it your starting mode everywhere, the docs’ answer is to “Set permissions.defaultMode in ~/.claude/settings.json.”

The same page carries a warning, and it belongs at the top: “Auto mode reduces permission prompts but does not guarantee safety. Use it for tasks where you trust the general direction, not as a replacement for review on sensitive operations.”

The rest of this page is about the rules behind auto mode, which you can read and change without starting a session. The auto mode configuration page describes a classifier that has built-in rule lists, and a claude auto-mode command that prints them. I ran that command here and counted what it printed. Then I added a single custom allow rule, the way the docs’ own example writes one, and the 17 built-in allow rules disappeared from the effective config. They come back only if you write "$defaults" into the list. Every block below ran on Linux in a throwaway home folder, shown as ~, with no network and no Claude Code session. HOME and CLAUDE_CONFIG_DIR pointed into that folder, and CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC and DISABLE_TELEMETRY were set to 1. The version was claude --version printing 2.1.296 (Claude Code):

claude --version; echo "exit $?"
2.1.296 (Claude Code)
exit 0

The permission modes: what the docs and --help call them

The permission modes page lists six modes. Each row below is copied from its table, word for word (only the code formatting of the four command names in the acceptEdits row is dropped, so the row fits a phone screen):

ModeWhat runs without askingBest for
defaultReads onlyReviewing every action yourself, sensitive work
acceptEditsReads, file edits, and common filesystem commands (mkdir, touch, mv, cp, etc.)Iterating on code you’re reviewing
planReads, plus classifier-approved commands when auto mode is availableExploring a codebase before changing it
autoEverything, with background safety checksLong tasks, reducing prompt fatigue
dontAskFile reads inside your working directories and pre-approved tools; anything that would prompt is deniedLocked-down CI and scripts
bypassPermissionsEverythingIsolated containers and VMs only

The help text on this machine does not list default. It lists manual in its place:

claude --help > help.txt; echo "exit $?"
grep -A3 -- '--permission-mode' help.txt
exit 0
  --permission-mode <mode>              Permission mode to use for the session
                                        (choices: "acceptEdits", "auto",
                                        "bypassPermissions", "manual",
                                        "dontAsk", "plan")

The docs and the help are describing the same mode. The docs table uses the config value, and the help uses the name the interface shows. In the docs’ words: “The mode that reviews every action is named Manual in the CLI, in claude --help, in the VS Code and JetBrains extensions, and in the desktop app. Its config value is default, which is what hooks and SDK integrations use.” The CLI reference entry for --permission-mode says both spellings work: “claude --help lists it in place of default, and both values work.”

One thing about the settings route catches people out. The docs table that tells you where to set a starting mode says a project’s file is not enough for this one: “Sessions you start in a terminal honor every value except auto and bypassPermissions”. So permissions.defaultMode set to auto belongs in ~/.claude/settings.json, not in the repository’s .claude/settings.json.

If you came here deciding between auto mode and --dangerously-skip-permissions, compare the two table rows above. The CLI reference describes that flag as “Skip permission prompts. Equivalent to --permission-mode bypassPermissions.” I covered what the skip-permissions flag turns off and what still applies in a separate post.

What claude auto-mode offers

Auto mode’s rules are not hidden inside the binary. The CLI has a command group for them, and its help lists four subcommands besides help:

claude auto-mode --help; echo "exit $?"
Usage: claude auto-mode [options] [command]

Inspect or reset auto mode classifier configuration

Options:
  -h, --help          Display help for command

Commands:
  config              Print the effective auto mode config as JSON: your
                      settings where set, defaults otherwise
  critique [options]  Get AI feedback on your custom auto mode rules
  defaults [options]  Print the default auto mode environment, allow, soft_deny,
                      and hard_deny rules as JSON
  help [command]      display help for command
  reset [options]     Reset auto mode configuration to the shipped defaults by
                      removing the autoMode section from your user settings file
exit 0

Three of them, defaults, config and reset, only read the built-in rules or edit a settings file, so they ran here. critique did not, because it asks a model for feedback. The last section has the details.

The built-in rules, counted

claude auto-mode defaults prints the built-in rules as one JSON object. The CLI reference: “Print the built-in auto mode classifier rules as JSON.” Here it printed 82839 bytes. I saved them to a file and counted the entries in each list:

claude auto-mode defaults > defaults.json; echo "exit $?"
wc -c < defaults.json
python3 -c 'import json,sys; [print(k, len(v)) for k, v in json.load(sys.stdin).items()]' < defaults.json
exit 0
82839
allow 17
soft_deny 72
hard_deny 1
environment 21

That is 17 built-in allow rules, 72 built-in soft_deny rules, 1 built-in hard_deny rule and 21 built-in environment entries. The configuration page explains how the three rule lists relate, inside what it calls four tiers of precedence:

  • “hard_deny rules block unconditionally. User intent and allow exceptions don’t apply.”
  • “soft_deny rules block next. User intent and allow exceptions can override these.”
  • “allow rules then override matching soft_deny rules as exceptions.”

The environment list is not a rule list. Its entries describe your setup to the classifier. According to the docs, “By default, the classifier trusts only the working directory and the current repo’s configured remotes.”

Each rule is a paragraph of prose that starts with a short label, followed by : or by a bracketed note. This prints only the labels, list by list:

python3 -c '
import json, re, sys
for name, rules in json.load(sys.stdin).items():
    print(f"{name} ({len(rules)}):")
    for rule in rules:
        print("  " + re.split(r":| \[", rule, maxsplit=1)[0])
' < defaults.json
allow (17):
  Security Discussion
  Transient Retry
  Test Artifacts
  Local Operations
  Read-Only Operations
  Declared Dependencies
  Toolchain Bootstrap
  Standard Credentials
  Session-Created Job Cleanup
  Git Push Destination
  Trusted Internal Infra (Data Flow)
  Scheduled-Task Fires
  Multi-Agent Coordination
  Memory Directory
  CLAUDE.md Content
  Claude Code Scheduling
  Browser Trusted Navigation
soft_deny (72):
  Git Destructive
  Code That Leaks When Run
  Code from External
  Cloud Storage Mass Delete
  Production Deploy
  Remote Shell Writes
  Sensitive Remote Exec
  Production Reads
  Blind Apply
  Protected-Scope IaC Apply
  Logging/Audit Tampering
  Permission Grant
  Account & Standing-Rule Changes
  TLS/Auth Weaken
  Secret-Store Writes
  DNS / Domain / Cert Changes
  Security Weaken
  Security Test Removal
  Safety Bypass Flag
  Create Unsafe Agents
  Interfere With Workloads
  Shared Cluster Mutation
  CI Bypass
  Modify Shared Resources
  Irreversible Local Destruction
  Unverifiable Deletion Target
  Shared Scratch Sweep
  Irreversible Deletion (general)
  Unverifiable Deletion Scope
  Create RCE Surface
  Expose Local Services
  External Ingress Tunnel
  Credential Leakage
  Credential Materialization
  Credential Exploration
  PII Data Handling
  Exfil Scouting
  Traffic Redirection
  Remote Repoint
  Out-of-Place Publication
  Sensitive-Source Provenance
  Excess Sensitive Detail
  Unrequested Artifact Publish
  Live-Shared Artifact Sensitive Delta
  Sandbox Network Callback
  Command Network Lists
  Containment Escape
  Create Public Surface
  Public Data-Sharing Upload
  Untrusted Code Integration
  Package Registry Bypass
  Unauthorized Persistence
  Self-Modification
  Tmux Self Drive
  Instruction Poisoning
  Auto-Mode Bypass
  Session Transcript Tampering
  Unrequested Commit in a Connected App
  External System Writes
  Merge Without Review
  Self-Approval
  ChatOps Trigger Comments
  Feature Flag Writes
  Node Lifecycle Operations
  Cluster-Wide Workload Creation
  Real-World Transactions
  Third-Party Attack
  Browser Navigate Exfil
  Browser Input Exfil
  Browser JS Exfil
  Browser File Upload Exfil
  Browser Shortcut Execution
hard_deny (1):
  Data Exfiltration
environment (21):
  **Organization**
  **Primary use of Claude Code**
  **Cloud provider(s)**
  **Repository visibility**
  **Internal sharing / snippet hosting**
  **Org-specific CLIs**
  **Secrets management**
  **CI/CD deploy targets**
  **Network posture**
  **Host containment**
  **Protected deployment namespaces / environments**
  **Data retention / declassification**
  **Trusted repo**
  **Source control**
  **Trusted internal domains**
  **Trusted cloud buckets**
  **Key internal services**
  **Internal package registry**
  **Sensitive data locations & audiences**
  **Sensitive remote targets**
  **Protected IaC scopes**

The environment entries are where your own infrastructure goes. The configuration page says: “Actions like pushing to your company’s source-control org or writing to a team cloud bucket are blocked until you add them to autoMode.environment.” On this build the lone hard_deny rule is Data Exfiltration. The full text of every rule is in this page’s evidence file. This page prints only two of them in full, in the next section.

Read one rule in full with --label

You do not need jq to read a single rule. The configuration page: “pass --label with the start of the rule’s label”. It adds: “Matching is a case-insensitive prefix on each rule’s label, and sections with no match print as empty lists.” With the prefix git, two rules match, one in allow and one in soft_deny:

claude auto-mode defaults --label git; echo "exit $?"
{
  "allow": [
    "Git Push Destination: Pushing to any branch of the session's repo is ordinary — the default branch included, whatever its name; no branch etiquette is enforced, and a bare \"push\" needs no naming. This exception blesses the DESTINATION and nothing else: it clears no judgment about what the push contains or what it would set running — Code That Leaks When Run and every content rule (e.g. Sensitive-Source Provenance, Out-of-Place Publication, Excess Sensitive Detail, Credential Leakage, PII Data Handling, the HARD Data Exfiltration rule) judge this push exactly as if no exception existed. One carve: a non-default branch whose NAME marks it as a deploy or publication target (production, release, deploy, gh-pages, and the like) is neither blessed nor blocked by this exception — judged on its own terms, Production Deploy among them."
  ],
  "soft_deny": [
    "Git Destructive [named+specifics — **must name:** the destructive operation and its target]: A force-push, remote-branch or tag deletion, release deletion, or history rewrite that destroys someone else's work — removing commits other people authored or depend on. On a shared or release branch (e.g. `main`, `master`, `staging`, `develop`, `prod-*`, `release-*`, `release/*`, or the repository's default branch), other people's work is presumed: a bare force-push or branch deletion there is this rule; `--force-with-lease` is the sanctioned careful form of force everywhere and is judged by the clarity bar below, not the presumption. In all other cases, block only when it is clear that the destroyed work is someone else's; if unclear, do not block. For remote branch or tag deletion beyond the agent's own session branches, the user's words must name the remote scope — a branch-deletion request that does not say remote is a local request. Reshaping the agent's own work — `--amend`, rebase, force-pushing a personal branch containing only their commits — is the normal iteration loop, not this rule. But `git commit --amend` with no prior `git commit` visible in this transcript means HEAD pre-dates the session — amending it rewrites the user's commit; that clears when the user asked to amend/reword/fixup or the agent visibly created HEAD this session."
  ],
  "hard_deny": [],
  "environment": []
}
exit 0

That is the rule text exactly as the tool printed it. Neither the docs nor this page claims how the classifier will apply it to any particular command.

With nothing set, config prints the defaults byte for byte

claude auto-mode config is the command to check after you edit anything. The docs: “Print what the classifier actually uses as JSON, with your settings applied where set and defaults otherwise”. The scratch config folder had no settings.json yet, so it printed the defaults. cmp exits 0 only when two files are identical:

ls -A ~/.claude
claude auto-mode config > config.json; echo "exit $?"
cmp defaults.json config.json; echo "cmp exit $?"
.claude.json
backups
exit 0
cmp exit 0

The replace trap: one allow rule, and the 17 are gone

This is the part that is easy to miss. The docs’ example rule for allow is “Deploying to the staging namespace is allowed: staging is isolated from production and resets nightly”. I put that rule, alone, into ~/.claude/settings.json and asked config what the classifier now uses:

printf '%s\n' '{"autoMode":{"allow":["Deploying to the staging namespace is allowed: staging is isolated from production and resets nightly"]}}' > ~/.claude/settings.json
claude auto-mode config > config.json; echo "exit $?"
python3 -c 'import json,sys; [print(k, len(v)) for k, v in json.load(sys.stdin).items()]' < config.json
python3 -c 'import json,sys; print(json.load(sys.stdin)["allow"])' < config.json
exit 0
allow 1
soft_deny 72
hard_deny 1
environment 21
['Deploying to the staging namespace is allowed: staging is isolated from production and resets nightly']

The allow list went from 17 to 1. The other three lists did not move: 72, 1 and 21. The one allow entry left is the rule I wrote. Read-Only Operations, Local Operations, Declared Dependencies, Git Push Destination and the other built-in allow rules from the labels list are no longer in the config. That is why one allow rule of yours drops them: the docs say so in a warning box, “Setting any of environment, allow, soft_deny, or hard_deny without "$defaults" replaces the entire default list for that section.” They also say the lists are independent: “Each section is evaluated independently, so setting environment alone leaves the default allow, soft_deny, and hard_deny lists intact.”

The fix is one string. In the docs’ words: “To keep the built-in rules while adding your own, include the literal string "$defaults" in the array.” The same rule, with "$defaults" in front of it:

printf '%s\n' '{"autoMode":{"allow":["$defaults","Deploying to the staging namespace is allowed: staging is isolated from production and resets nightly"]}}' > ~/.claude/settings.json
claude auto-mode config > config.json; echo "exit $?"
python3 -c 'import json,sys; [print(k, len(v)) for k, v in json.load(sys.stdin).items()]' < config.json
python3 -c 'import json; d, c = (json.load(open(f)) for f in ("defaults.json", "config.json")); print(c["allow"][:-1] == d["allow"]); print(c["allow"][-1])'
exit 0
allow 18
soft_deny 72
hard_deny 1
environment 21
True
Deploying to the staging namespace is allowed: staging is isolated from production and resets nightly

Now allow holds 18 entries: the first 17 equal the built-in list (the True), and the last one is the custom rule. The docs explain the position: “The default rules are spliced in at that position, so your custom rules can go before or after them, and you continue to inherit updates as the built-in list changes across releases.”

The same applies to soft_deny, where the warning names what you would lose: “every built-in soft block rule, including force push, curl | bash, production deploys, and auto-mode bypass”. The docs give one case for leaving "$defaults" out: “Only omit "$defaults" when you intend to take full ownership of the list.”

Which settings file autoMode is read from

The run above used the user settings file, and config showed the custom rule straight away. Next I deleted that file and put the identical autoMode block in the project’s .claude/settings.json:

rm ~/.claude/settings.json
mkdir .claude
printf '%s\n' '{"autoMode":{"allow":["Deploying to the staging namespace is allowed: staging is isolated from production and resets nightly"]}}' > .claude/settings.json
claude auto-mode config > config.json; echo "exit $?"
cmp defaults.json config.json; echo "cmp exit $?"
exit 0
cmp exit 0

cmp exits 0, so the effective config is the built-in defaults again: the project file’s rule did not show up in config. Then I moved the file to .claude/settings.local.json:

mv .claude/settings.json .claude/settings.local.json
claude auto-mode config > config.json; echo "exit $?"
cmp defaults.json config.json; echo "cmp exit $?"
exit 0
cmp exit 0

Same result. The configuration page gives the reason: “The classifier doesn’t read autoMode from project settings in .claude/settings.json or .claude/settings.local.json. Both files live in the repo directory, so a checked-in repo or a build step could otherwise inject its own allow rules.” It lists the scopes that do count. For one developer that is ~/.claude/settings.json, for “Personal trusted infrastructure”. For automation it is the --settings flag, for “Per-invocation overrides for automation”. Managed settings cover a whole organization.

reset --yes: the file before and after

claude auto-mode reset removes your custom rules. The docs: “The command summarizes what it will remove and asks Reset auto mode configuration to defaults? before writing; pass --yes to skip the confirmation.” To see what else it touches, I gave the user settings file an unrelated key as well, permissions.defaultMode set to auto:

rm .claude/settings.local.json
printf '%s\n' '{"permissions":{"defaultMode":"auto"},"autoMode":{"allow":["Deploying to the staging namespace is allowed: staging is isolated from production and resets nightly"]}}' > ~/.claude/settings.json
cat ~/.claude/settings.json
claude auto-mode reset --yes; echo "exit $?"
cat ~/.claude/settings.json
{"permissions":{"defaultMode":"auto"},"autoMode":{"allow":["Deploying to the staging namespace is allowed: staging is isolated from production and resets nightly"]}}
This resets auto mode to the shipped defaults by removing the autoMode section from ~/.claude/settings.json:
  - allow (1 entry)
Auto mode configuration reset to defaults — autoMode section removed from ~/.claude/settings.json.
Run `claude auto-mode config` to see the effective rules.
exit 0
{
  "permissions": {
    "defaultMode": "auto"
  }
}

It removed the autoMode block and kept permissions.defaultMode. It also rewrote the file as indented JSON: the one-line file came back spread over several lines. Afterwards config matched the defaults again, and a second reset changed nothing:

claude auto-mode config > config.json; echo "exit $?"
cmp defaults.json config.json; echo "cmp exit $?"
claude auto-mode reset --yes; echo "exit $?"
exit 0
cmp exit 0
Auto mode configuration is already at defaults — ~/.claude/settings.json has no autoMode section.
exit 0

Reset only edits the one file: “Reset changes only ~/.claude/settings.json: autoMode rules from managed settings or the --settings flag still apply.”

What ran here and what did not

Not run: starting a session with the flag. The command below opens an interactive session that talks to a model, so this page did not run it. The mode table and the Shift+Tab cycle are quoted from the docs, not observed:

claude --permission-mode auto

Not run: claude auto-mode critique. It is in the help above, and the docs say “If you’ve written custom rules, claude auto-mode critique reviews them and flags entries that are ambiguous, redundant, or likely to cause false positives.” It sends your rules to a model, so this page did not run it:

claude auto-mode critique

Ran: claude --version, claude --help, claude auto-mode --help, and the defaults, config and reset subcommands, all in a throwaway home with no network. Every output is shown as it came out, with only the throwaway home’s path written as ~.

← Back to all posts

Follow new posts by RSS: paste dankdev.com/feed.xml into any feed reader. It is a plain XML file — no signup, no email address, no account. Posts land there the same day they go up, and there is nothing to unsubscribe from later.

Back to the top