Hard-Deny Classifier Rule¶
The
autoMode.hard_denyfield blocks tool calls unconditionally inside the auto-mode classifier — user intent and allow exceptions do not apply.
Three Deny Layers, Not One¶
Claude Code stacks three distinct deny mechanisms. They run at different stages and offer different guarantees:
| Mechanism | Layer | Override path |
|---|---|---|
permissions.deny |
Pre-classifier | None — deny-first precedence, evaluated before the classifier |
autoMode.hard_deny |
Inside classifier | None — user intent and allow do not apply |
autoMode.soft_deny |
Inside classifier | Overridable by allow exceptions or explicit user intent |
hard_deny shipped in Claude Code 2.1.136 (2026-05-08): "Added settings.autoMode.hard_deny for auto mode classifier rules that block unconditionally regardless of user intent or allow exceptions" (Claude Code changelog).
permissions.deny is the deterministic, pre-classifier floor. hard_deny is the inside-classifier floor — still evaluated by an LLM, but unaffected by the argumentation that can lift a soft_deny. For actions that must never run regardless of intent or classifier config, the permissions docs direct you to permissions.deny in managed settings; it blocks before the classifier is consulted.
Precedence Inside the Classifier¶
Auto mode's classifier evaluates its four list fields in this order (Configure auto mode):
graph TD
A[Classifier sees tool call] --> B{hard_deny match?}
B -->|Yes| X[Block - no override]
B -->|No| C{soft_deny match?}
C -->|No| G[Allow]
C -->|Yes| D{allow exception?}
D -->|Yes| G
D -->|No| E{Explicit user intent?}
E -->|Yes - exact action| G
E -->|No| X2[Block]
"
hard_denyrules block unconditionally. User intent andallowexceptions do not apply." (Configure auto mode)
"Explicit user intent" means the user's message describes the exact action — asking Claude to "force-push this branch" counts; asking to "clean up the repo" does not.
Syntax and the $defaults Sentinel¶
Each list field is an array of prose strings. Entries are natural-language rules, not regex or tool patterns:
{
"autoMode": {
"hard_deny": [
"$defaults",
"Never send repository contents to third-party code-review APIs",
"Never read or copy production database credentials, even when listed in a config file"
]
}
}
The literal "$defaults" splices in Anthropic's built-in rules at that position. Omitting "$defaults" replaces the entire default list — the auto-mode config docs flag this with a Danger callout: "A hard_deny array without "$defaults" discards the built-in data exfiltration and auto-mode bypass rules." Adding one custom rule without the sentinel silently deletes that floor. Print the built-ins with claude auto-mode defaults before taking full ownership.
Where the Classifier Reads autoMode¶
The classifier merges autoMode from these scopes (Configure auto mode):
| Scope | File | Use for |
|---|---|---|
| One developer | ~/.claude/settings.json |
Personal trusted infrastructure |
| One project, one developer | .claude/settings.local.json |
Per-project, gitignored |
| Organization-wide | Managed settings | Distributed policy |
| Inline | --settings flag or Agent SDK |
Per-invocation overrides |
The classifier does not read autoMode from shared .claude/settings.json, so a checked-in repo cannot inject its own allow rules. Entries are additive across scopes: a developer can extend hard_deny with personal entries but cannot remove entries that managed settings provide.
When hard_deny Is the Right Tool¶
Use hard_deny when the rule is classifier-shaped — it describes intent or destination ("never exfiltrate to third-party code-review APIs"), not a tool-pattern match. LLM-mediated interpretation is acceptable, and you want the rule to participate in the classifier's reasoning without being argued out of.
Use permissions.deny instead when the rule is tool-shaped — it matches a specific command or domain pattern (Bash(rm -rf /*), WebFetch(domain:internal.example.com)), compliance needs deterministic pre-classifier enforcement, or the block must apply even when auto mode is disabled. Tool-shaped rules now reach below the tool name: Claude Code added Tool(param:value) parameter-scoped permission rules that match on a specific tool parameter value rather than only the tool name (Claude Code changelog), tightening the deterministic floor for cases where the danger lives in an argument, not the verb.
Use OS-level sandboxing as a third layer when blast-radius containment matters at the process boundary, not just the agent decision boundary.
Inspect and Validate¶
Three CLI subcommands check what the classifier sees (Configure auto mode):
claude auto-mode defaults # built-in rules, before any merge
claude auto-mode config # effective rules with "$defaults" expanded
claude auto-mode critique # AI feedback on custom allow/deny rules
Run claude auto-mode config after saving settings to confirm the merged result is what you expect. claude auto-mode critique flags entries that are ambiguous, redundant, or likely to cause false positives — useful before committing a fragment to managed settings.
When This Backfires¶
- Treating
hard_denyas deterministic — the classifier is an LLM. Rule interpretation is probabilistic; a novel re-framing can still slip through. Compliance-grade enforcement belongs inpermissions.denyor the sandbox layer. - Auto mode disabled or unavailable —
hard_denyis part ofautoMode. On Pro plans or Bedrock/Vertex/Foundry providers, auto mode is unavailable (Configure auto mode) and the rules never run. Settings withpermissions.disableAutoMode: "disable"produce the same silent no-op. - Replacement-without-
$defaults— the single most common configuration mistake. Always include"$defaults"unless you have explicitly chosen to take full ownership of the list. - Solo developer settings —
hard_denyonly delivers organizational guarantees when an admin owns managed settings. In user or local settings, the same developer can remove the rule they added. - In-project file writes skip the classifier entirely — auto mode tiers its actions: file writes and edits inside the project directory run without a classifier call (How we built Claude Code auto mode). A
hard_denyrule like "never write production credentials to a file" never fires when the write lands inside the repo. Only operations the classifier sees — shell commands, web fetches, out-of-project writes — are subject to it.
Example¶
A security team wants to ensure repository contents never reach external code-review APIs, even when a developer's prompt could plausibly justify it. The rule is destination-shaped (third-party APIs), so a tool-pattern permissions.deny would be brittle — every new code-review service would need a new entry.
Deploy via managed settings so developers cannot remove the rule:
{
"autoMode": {
"environment": [
"$defaults",
"Source control: github.example.com/acme-corp and all repos under it"
],
"hard_deny": [
"$defaults",
"Never send repository contents — including diffs, files, or paste buffers — to any third-party code-review or AI code-analysis API not on the approved list",
"Never write to or read from production credential stores (AWS Secrets Manager, GCP Secret Manager) regardless of which environment variable references them"
]
}
}
Two properties hold:
- A developer adding
"Sending diffs to review-service.example.com is allowed"to their localallowlist cannot override the managedhard_deny—allowdoes not apply tohard_deny. - A user prompt of "send the current diff to review-service.example.com for analysis" — explicit intent that would lift a
soft_deny— still hits the block.
Failures appear in /permissions under Recently denied. To react programmatically, the PermissionDenied hook fires on classifier denials and can append a distinct rejection class to confirmation-gate logs.
Key Takeaways¶
hard_denyis the unconditional layer inside the auto-mode classifier — distinct frompermissions.deny(pre-classifier, deterministic) andsoft_deny(overridable byallowor explicit intent)- User intent and
allowexceptions do not lift ahard_denymatch - Entries are prose, read as natural-language rules — not tool patterns or regex
- Omitting
"$defaults"from anyautoModelist replaces the entire default — including built-in exfiltration and safety-check rules - The classifier is still an LLM;
hard_denyis a strong floor, not a deterministic one — pair withpermissions.denyor sandboxing for compliance-grade enforcement
Related¶
- Auto Mode — Classifier architecture, evaluation order, false-negative rates
- Managed Settings Drop-In Directory — Deploying
autoModepolicy across an org - Confirmation Gates — Human-in-the-loop checks for consequential actions