Settings, Permissions & Hooks
Every Claude Code session eventually asks the same question of its operator: how much should this agent be allowed to do without stopping to ask? Too little autonomy and you've built an expensive autocomplete that interrupts you every ten seconds. Too much and a single misread instruction can run a destructive command, leak a secret, or overwrite a file nobody reviewed. settings.json, the permission system, and hooks are the three mechanisms Claude Code gives you to answer that question precisely, at the granularity of an individual command or file path rather than a single global toggle.
This subject goes one layer deeper than the basics: not just that a permissions block exists, but exactly how a rule like Bash(npm run test *) gets matched against a real shell command, what "deny beats ask beats allow" actually means when two rules disagree, and what a hook script can and cannot do at each of the two dozen points in Claude Code's lifecycle where it can intervene. This is the material a senior engineer needs to configure a shared, team-wide Claude Code setup that is both fast to work in and safe to leave unattended — which is exactly the kind of judgment call that shows up in interviews about operating agentic tools at scale.
The settings-file hierarchy itself (user, project, local, managed) and the basic idea of CLAUDE.md as durable context are covered in the CLAUDE.md and Context Configuration subject — this subject assumes you know that .claude/settings.json exists and focuses on what actually goes inside it.
Settings Precedence, Precisely
settings.json can live in five places, and Claude Code merges them in a specific order (highest precedence first):
| Precedence | Scope | Location |
|---|---|---|
| 1 (highest) | Managed / enterprise policy | Deployed by IT — /Library/Application Support/ClaudeCode/ (macOS), /etc/claude-code/ (Linux/WSL), HKLM\SOFTWARE\Policies\ClaudeCode or C:\Program Files\ClaudeCode\ (Windows) |
| 2 | Command-line arguments | Flags passed to claude for this invocation |
| 3 | Local | .claude/settings.local.json — gitignored, yours alone |
| 4 | Project | .claude/settings.json — committed, shared with the team |
| 5 (lowest) | User | ~/.claude/settings.json — yours, every project |
Most individual settings follow simple override-by-priority: if both project and user settings set model, the project's value wins. permissions rules are the deliberate exception — allow, ask, and deny rules merge across every scope instead of the higher scope replacing the lower one. A deny rule set anywhere in the hierarchy stays in effect no matter what a lower-precedence file says; you cannot accidentally "un-deny" something a teammate or your IT department blocked. This is also why managed settings can enforce permissions.disableBypassPermissionsMode and permissions.disableAutoMode — once an org sets either to "disable", no project or user file can re-enable that mode.
Managed settings can go further than merely outranking the other scopes: a handful of managed-only keys remove the other scopes from the conversation entirely. allowManagedPermissionRulesOnly: true means user and project allow/ask/deny rules are no longer even read — only managed rules apply. allowManagedHooksOnly: true does the same for hooks. These exist for organizations that need a genuinely non-negotiable floor, not just a highest-precedence one; an ordinary managed deny rule already can't be overridden, but it can still coexist with additional rules from lower scopes; allowManagedPermissionRulesOnly removes the coexistence entirely.