Skip to content
🎓 Find your path Subscribe

settings.json & Permissions Config

Tier 1 · Fundamentals 8 min read

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.


Claude Code uses a layered config system. Settings in narrower scopes override broader ones:

  1. Global: ~/.claude/settings.json — applies everywhere.
  2. Project: .claude/settings.json in the working directory — overrides global for this project.
  3. 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.

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.

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.

The allow/deny list for tool calls. Entries use glob patterns against the tool name and its arguments.

  • allow entries let the agent run those commands without prompting you. Bash(pytest*) means any pytest invocation gets auto-approved.
  • deny entries hard-block the command regardless of what you say in the session. Bash(rm -rf*) in the deny list means the agent literally cannot run rm -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.

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.

A practical guide to what belongs in each layer:

WhatWhere
Rules + standing instructionsCLAUDE.md
Always-allowed commands (no prompt needed)settings.json permissions.allow
Hard-blocked commandssettings.json permissions.deny
Hookssettings.json hooks
Pluginssettings.json enabledPlugins
Machine-specific secrets/pathssettings.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.