settings.json & Permissions Config
Claude Code reads two types of startup files: CLAUDE.md (instructions for the model) and settings.json (configuration for the harness itself). This page is about the latter. settings.json controls what the agent is allowed to do, what plugins and hooks are active, and which environment variables are in scope — without putting any of that in the conversation.
The settings hierarchy
Section titled “The settings hierarchy”Claude Code uses a layered config system. Settings in narrower scopes override broader ones:
- Global:
~/.claude/settings.json— applies everywhere. - Project:
.claude/settings.jsonin the working directory — overrides global for this project. - Local (gitignored):
.claude/settings.local.json— machine-specific overrides not tracked in the repo (secrets, local paths).
The layers stack. If global settings allow npm commands and a project-local file adds a git allowlist entry, both apply in that project.
Key fields
Section titled “Key fields”A typical settings.json looks like this:
{ "enabledPlugins": { "telegram@claude-plugins-official": true }, "permissions": { "allow": [ "Bash(npm run test:*)", "Bash(git status)", "Bash(git diff*)", "Bash(pytest*)", "Bash(ruff check*)" ], "deny": [ "Bash(git push --force*)", "Bash(rm -rf*)" ] }, "env": { "NODE_ENV": "development" }, "hooks": { "Stop": [ { "matcher": "", "hooks": [ { "type": "command", "command": "/opt/homebrew/bin/python3.12 /Users/jd/agent-system/scripts/telegram-stop-hook.py" } ] } ] }}Let’s break down each section.
enabledPlugins
Section titled “enabledPlugins”Activates Claude Code plugins. The Telegram plugin shown above is what turns the agent into a Telegram bot — it adds the inbound routing and reply tool. Without enabledPlugins, the plugin is installed but silent.
permissions
Section titled “permissions”The allow/deny list for tool calls. Entries use glob patterns against the tool name and its arguments.
allowentries let the agent run those commands without prompting you.Bash(pytest*)means anypytestinvocation gets auto-approved.denyentries hard-block the command regardless of what you say in the session.Bash(rm -rf*)in the deny list means the agent literally cannot runrm -rf, even if you ask it to.
The allow list is how you eliminate the constant “May I run git status?” prompts that slow down interactive work. The deny list is how you enforce hard rules at the harness level, not just in CLAUDE.md.
Sets environment variables for the session. Unlike system-level env vars, these are injected directly into the Claude Code runtime. Useful for NODE_ENV, feature flags, or non-secret configuration values.
For secrets (API keys, tokens), use .claude/settings.local.json (gitignored) or your shell’s .env file. Don’t put secrets in a tracked settings.json.
This is where hooks are wired (detailed in the hooks article). Each hook entry names a lifecycle event (Stop, PreToolUse), a matcher (empty string matches all sessions), and one or more commands to run.
The /config command
Section titled “The /config command”You can view and edit settings interactively with /config inside a session. It opens a TUI that lets you toggle permissions, adjust settings, and save without hand-editing the JSON. For simple one-off changes, /config is faster. For version-controlled, repeatable configuration, hand-editing the file and committing it is better.
What goes where
Section titled “What goes where”A practical guide to what belongs in each layer:
| What | Where |
|---|---|
| Rules + standing instructions | CLAUDE.md |
| Always-allowed commands (no prompt needed) | settings.json permissions.allow |
| Hard-blocked commands | settings.json permissions.deny |
| Hooks | settings.json hooks |
| Plugins | settings.json enabledPlugins |
| Machine-specific secrets/paths | settings.local.json (gitignored) |
| Project env vars (non-secret) | settings.json env |
Keep CLAUDE.md for model-visible instructions. Keep settings.json for harness-level enforcement. They serve different layers and shouldn’t duplicate each other.
Next: Git Discipline for Agents — why branch-first, why commit conventions matter, and why agents should not touch the default branch.