The Complete Guide to Claude Code settings.json: From Permission Control to Team Collaboration
Introduction
In the Complete CLAUDE.md Guide, I mentioned: CLAUDE.md tells Claude "what to do," settings.json tells Claude "how to do it."
Subsequent posts covered specific parts of settings.json: the Hooks Guide covered the hooks field, and the MCP Guide covered the mcpServers field. But settings.json as a unified system — its scope hierarchy, merge mechanics, and permission system — has never been covered.
Especially the permission system. It's the most critical and most error-prone part of settings.json, and previous posts didn't touch it at all.
This post covers settings.json thoroughly.
1. The settings.json Overview
1.1 One-Line Definition
settings.json is Claude Code's behavior configuration file. It doesn't control "what Claude should know" (that's CLAUDE.md's job) — it controls "what Claude is allowed to do, which model to use, and how to invoke tools."
1.2 Complete Schema Overview
All top-level fields in settings.json:
| Field | Type | Purpose | Details |
|---|---|---|---|
permissions | object | Tool invocation permissions | Section 3 of this post |
hooks | object | Lifecycle hooks | Hooks Guide |
mcpServers | object | MCP server configuration | MCP Guide |
env | object | Environment variable injection | Section 4 of this post |
model | string | Default model | Section 5 of this post |
maxTokens | number | Max output tokens | Section 5 of this post |
apiProvider | string | API provider | Section 5 of this post |
customApiHeaders | object | Custom API request headers | Section 5 of this post |
1.3 Relationship with Other Config Files
Claude Code has four types of configuration files, each with a distinct role:
| File | Purpose | Analogy |
|---|---|---|
CLAUDE.md | Project knowledge, coding standards, workflow instructions | Employee handbook |
settings.json | Permissions, tools, model, environment variables | Security policy + IT config |
.claude/commands/ | Reusable prompt templates | SOP documents |
.claudeignore | Exclude files/directories | .gitignore |
A common mistake is stuffing everything into CLAUDE.md. Remember: if it's a "rule," put it in CLAUDE.md; if it's a "configuration," put it in settings.json.
2. Five Scopes and Merge Mechanics
2.1 The Five Layers
settings.json has five scopes, from highest to lowest priority:
① Enterprise (highest priority)
/etc/claude-code/managed-settings.json (Linux/Mac)
② Session
Passed via --settings flag at startup
③ Project Shared (team-shared)
.claude/settings.json (committed to Git)
④ Project Local (personal local)
.claude/settings.local.json (not committed to Git)
⑤ User Global (lowest priority)
~/.claude/settings.json
The three most commonly used are: User Global (personal preferences), Project Shared (team standards), and Project Local (personal overrides).
2.2 Merge Rules
Different field types have different merge strategies:
Array fields (permissions) — concatenation
// User Global: ~/.claude/settings.json
{
"permissions": {
"allow": ["Read"]
}
}
// Project Shared: .claude/settings.json
{
"permissions": {
"allow": ["Bash(npm test)"]
}
}
// Merged result: both allow arrays concatenated
// allow: ["Read", "Bash(npm test)"]Object fields (env, mcpServers) — deep merge
// User Global
{
"env": {
"EDITOR": "vim"
}
}
// Project Shared
{
"env": {
"NODE_ENV": "development"
}
}
// Merged result: both objects merged
// env: { "EDITOR": "vim", "NODE_ENV": "development" }When keys collide, higher-priority scope wins.
Scalar fields (model, maxTokens) — direct override
// User Global
{ "model": "claude-sonnet-4-6" }
// Project Shared
{ "model": "claude-opus-4-6" }
// Merged result: higher-priority Project Shared wins
// model: "claude-opus-4-6"2.3 Practical Scenarios: Which Scope to Use
| Scenario | Recommended Scope | Reason |
|---|---|---|
| Personal model preference | User Global | Applies across projects |
| Team permission rules | Project Shared | Committed to Git, shared |
| Personal API keys | Project Local | Not committed to Git |
| CI/CD environment config | Session | Passed via startup args |
| Company security policy | Enterprise | Highest priority, cannot be overridden |
| Project-specific MCP servers | Project Shared | Team-shared |
| Personal MCP servers | User Global | Available across projects |
| Personal tokens/secrets | Project Local or User Global | Never commit to Git |
3. The Permission System In Depth
This is the most important part of settings.json. The permission system determines which tools Claude can invoke and which commands it can execute.
3.1 Three Permission Types
{
"permissions": {
"allow": [], // Auto-approve, no confirmation prompt
"deny": [], // Reject outright, Claude can't see the tool
"ask": [] // Prompt for confirmation each time (default)
}
}Tools not explicitly configured default to ask — every invocation triggers a confirmation prompt.
Three types and their use cases:
| Type | Use Case | Example |
|---|---|---|
allow | Operations you fully trust | Reading files, running tests |
deny | Operations that must never happen | Deleting production DB, force push |
ask | Operations requiring human judgment | Writing files, installing deps (default) |
3.2 Tool Names and Pattern Syntax
The core of permission rules is tool name + argument pattern.
Base tool names:
| Tool Name | Purpose |
|---|---|
Read | Read files |
Edit | Edit files |
Write | Create/overwrite files |
Bash | Execute shell commands |
WebFetch | Access URLs |
WebSearch | Web search |
Argument pattern matching:
The Bash tool supports argument patterns for fine-grained command control:
{
"permissions": {
"allow": [
"Read", // Allow all file reads
"Bash(npm test)", // Only allow npm test
"Bash(npm run *)", // Allow all npm run subcommands
"Bash(git log *)", // Allow git log
"Bash(git diff *)", // Allow git diff
"Bash(cd * && cat *)" // Allow cd + cat combos
],
"deny": [
"Bash(rm -rf *)", // Forbid recursive deletion
"Bash(git push --force *)" // Forbid force push
]
}
}Glob pattern rules:
*matches any characters (doesn't cross argument boundaries)- Patterns match against the actual arguments Claude passes to the tool
- For
Bash, the argument is the complete shell command string
MCP tool permissions:
MCP tools use the mcp__<server>__<tool> format:
{
"permissions": {
"allow": [
"mcp__github__create_issue", // Allow creating issues
"mcp__filesystem__read_file" // Allow reading files
],
"deny": [
"mcp__github__delete_repository" // Forbid repo deletion
]
}
}3.3 Evaluation Order and Priority
Permission evaluation follows a strict order:
1. Check deny list → match = reject (highest priority)
2. Check allow list → match = approve
3. No match → default ask (confirmation prompt)
deny always takes priority over allow. This means:
{
"permissions": {
"allow": ["Bash(git *)"], // Allow all git commands
"deny": ["Bash(git push --force *)"] // But forbid force push
}
}
// Result: git push --force is rejected, other git commands are allowedCross-scope behavior:
When multiple scopes have permission configs, all deny rules are merged and evaluated first, then all allow rules. This means a deny in any scope cannot be overridden by an allow in another scope.
// Project Shared: .claude/settings.json
{
"permissions": {
"deny": ["Bash(rm -rf *)"]
}
}
// User Global: ~/.claude/settings.json
{
"permissions": {
"allow": ["Bash(rm -rf *)"] // Attempting to override project deny
}
}
// Result: rm -rf is still rejected. deny cannot be overridden.3.4 Common Permission Templates
Convenience mode (solo development):
{
"permissions": {
"allow": [
"Read",
"Edit",
"Write",
"Bash(npm *)",
"Bash(npx *)",
"Bash(git status)",
"Bash(git diff *)",
"Bash(git log *)",
"Bash(git add *)",
"Bash(git commit *)"
],
"deny": [
"Bash(git push --force *)",
"Bash(rm -rf *)"
]
}
}Strict mode (team projects):
{
"permissions": {
"allow": [
"Read",
"Bash(npm test *)",
"Bash(npm run lint *)",
"Bash(git status)",
"Bash(git diff *)",
"Bash(git log *)"
],
"deny": [
"Bash(git push *)",
"Bash(rm *)",
"Bash(npm publish *)",
"Bash(curl *)",
"Bash(wget *)"
]
}
}CI/CD mode (automation pipelines):
{
"permissions": {
"allow": [
"Read",
"Edit",
"Write",
"Bash(npm *)",
"Bash(git *)"
],
"deny": [
"Bash(git push --force *)",
"Bash(rm -rf /)",
"WebFetch",
"WebSearch"
]
}
}CI/CD mode is more permissive because pipelines are controlled environments. But it still forbids force push and dangerous deletions.
3.5 Debugging Permissions
View effective permissions:
Type /permissions in Claude Code to see the merged permission list, including which scope each rule comes from.
Common pitfalls:
-
Pattern mismatch:
Bash(npm test)only matches the exact stringnpm test, notnpm test -- --watch. UseBash(npm test*)orBash(npm test *). -
Overly broad deny:
deny: ["Bash(git *)"]blocks ALL git commands, includinggit statusandgit diff. Only deny dangerous operations. -
Missing MCP tool prefix: MCP tools must use the
mcp__<server>__<tool>format. Writing just the tool name won't match. -
Secrets in Project Shared:
.claude/settings.jsongets committed to Git. API keys, tokens, and other secrets belong in.claude/settings.local.json.
4. Environment Variable Configuration
4.1 The env Field
The env field in settings.json injects environment variables into Claude Code's runtime:
{
"env": {
"NODE_ENV": "development",
"DEBUG": "true",
"API_BASE_URL": "https://api.example.com"
}
}These variables are injected when Claude Code starts and apply to all Bash commands.
Common uses:
- Set development environment variables (
NODE_ENV,DEBUG) - Configure API endpoints
- Set toolchain paths
4.2 Top-Level env vs MCP Server env
There are two levels of env, which can be confusing:
{
// Top-level env: applies to all Bash commands
"env": {
"GLOBAL_VAR": "value"
},
// MCP Server env: only applies to that server's process
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_TOKEN": "ghp_xxx" // Only the github server sees this
}
}
}
}Top-level env affects all Bash commands Claude Code executes. MCP Server env only affects that server's process — more secure.
4.3 Environment Variable Scoping
env follows object merge rules — multiple scopes' env fields are deep-merged, with higher-priority scopes overriding same-named keys:
// User Global
{ "env": { "EDITOR": "vim", "LANG": "en_US" } }
// Project Shared
{ "env": { "NODE_ENV": "development", "LANG": "zh_CN" } }
// Merged result
// { "EDITOR": "vim", "NODE_ENV": "development", "LANG": "zh_CN" }
// LANG overridden by Project Shared5. Model and API Configuration
5.1 Model Configuration
Specify the model Claude Code uses via the model field:
{
"model": "claude-sonnet-4-6"
}Common models:
| Model ID | Characteristics |
|---|---|
claude-opus-4-6 | Most capable, ideal for complex tasks |
claude-sonnet-4-6 | Balanced performance and cost |
claude-haiku-4-5 | Fastest, ideal for simple tasks |
You can also use the /model command during a session to switch models temporarily without affecting settings.json.
5.2 maxTokens
Limit the maximum tokens per response:
{
"maxTokens": 16384
}The default is usually sufficient. Only increase it when generating very long content. Note: this value cannot exceed the model's own limit.
5.3 API Provider Configuration
Claude Code defaults to the Anthropic API but also supports Amazon Bedrock and Google Vertex AI:
Anthropic (default):
{
"apiProvider": "anthropic"
// Uses ANTHROPIC_API_KEY environment variable
}Amazon Bedrock:
{
"apiProvider": "bedrock",
"env": {
"AWS_REGION": "us-east-1",
"AWS_PROFILE": "my-profile"
}
}Google Vertex AI:
{
"apiProvider": "vertex",
"env": {
"CLOUD_ML_REGION": "us-east5",
"ANTHROPIC_VERTEX_PROJECT_ID": "my-project-id"
}
}Custom API headers:
{
"customApiHeaders": {
"X-Custom-Header": "value",
"Authorization": "Bearer custom-token"
}
}Useful when accessing through API gateways or proxies.
6. Enterprise Management
6.1 Managed Settings
In enterprise environments, administrators can enforce security policies through Managed Settings:
Linux/Mac: /etc/claude-code/managed-settings.json
This is the highest-priority scope. User and project configurations cannot override it.
// /etc/claude-code/managed-settings.json
{
"permissions": {
"deny": [
"Bash(curl *)",
"Bash(wget *)",
"Bash(npm publish *)",
"Bash(git push --force *)",
"WebFetch",
"WebSearch"
]
},
"model": "claude-sonnet-4-6"
}6.2 Typical Enterprise Policies
Block network access:
{
"permissions": {
"deny": [
"Bash(curl *)",
"Bash(wget *)",
"Bash(ssh *)",
"WebFetch",
"WebSearch"
]
}
}Enforce a specific model (cost control):
{
"model": "claude-sonnet-4-6"
}Since the Enterprise scope has the highest priority, even if a user configures claude-opus-4-6 in User Global, claude-sonnet-4-6 takes effect.
Restrict MCP Servers:
{
"mcpServers": {},
"permissions": {
"deny": [
"mcp__*"
]
}
}By denying all mcp__* patterns, you can block all MCP Server usage.
7. Walkthrough: Complete Flutter Project Configuration
7.1 Project Context
Imagine you're working on a Flutter team:
- Tech stack: Flutter + Riverpod + Firebase
- Team of 5, collaborating via GitHub
- CI/CD pipeline in place
- Using GitHub MCP Server for issue management
7.2 Team Shared Configuration
.claude/settings.json (committed to Git):
{
"permissions": {
"allow": [
"Read",
"Bash(flutter test *)",
"Bash(flutter analyze *)",
"Bash(dart format *)",
"Bash(dart fix *)",
"Bash(flutter pub get)",
"Bash(git status)",
"Bash(git diff *)",
"Bash(git log *)",
"mcp__github__list_issues",
"mcp__github__get_issue",
"mcp__github__create_issue"
],
"deny": [
"Bash(git push --force *)",
"Bash(rm -rf *)",
"Bash(flutter pub publish *)",
"mcp__github__delete_repository"
]
},
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "dart format --line-length 80 $CLAUDE_FILE_PATH"
}
]
}
]
},
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_TOKEN": ""
}
}
},
"env": {
"FLUTTER_TEST_CONCURRENCY": "4"
}
}Note that GITHUB_TOKEN is left empty — each team member fills in the actual value locally.
7.3 Personal Local Configuration
.claude/settings.local.json (not committed to Git):
{
"permissions": {
"allow": [
"Edit",
"Write",
"Bash(git add *)",
"Bash(git commit *)"
]
},
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_TOKEN": "ghp_xxxxxxxxxxxx"
}
}
},
"model": "claude-opus-4-6"
}This does three things:
- Grants yourself additional write permissions (team config only allows Read)
- Fills in the real GitHub Token
- Sets your preferred model
7.4 Global Configuration
~/.claude/settings.json (applies across projects):
{
"permissions": {
"allow": [
"Read",
"Bash(cat *)",
"Bash(ls *)",
"Bash(which *)",
"Bash(echo *)"
]
},
"env": {
"EDITOR": "code",
"LANG": "zh_CN.UTF-8"
}
}7.5 The Merged Result
After merging all three scopes, the effective configuration:
allow (concatenated):
- Read ← User Global + Project Shared
- Bash(cat *), Bash(ls *)... ← User Global
- Bash(flutter test *)... ← Project Shared
- Edit, Write ← Project Local
- Bash(git add *), Bash(git commit *) ← Project Local
- mcp__github__list_issues... ← Project Shared
deny (concatenated):
- Bash(git push --force *) ← Project Shared
- Bash(rm -rf *) ← Project Shared
- Bash(flutter pub publish *) ← Project Shared
- mcp__github__delete_repository ← Project Shared
model: "claude-opus-4-6" ← Project Local overrides
env (deep merged):
- EDITOR: "code" ← User Global
- LANG: "zh_CN.UTF-8" ← User Global
- FLUTTER_TEST_CONCURRENCY: "4" ← Project Shared
mcpServers (deep merged):
- github: GITHUB_TOKEN = "ghp_xxx" ← Project Local overrides Project Shared's empty value
This is the power of multi-scope configuration: team shares safety rules, individuals customize preferences, and secrets stay out of the repo.
8. Frequently Asked Questions
Q1: How exactly should settings.json and CLAUDE.md divide responsibilities?
Simple principle:
- CLAUDE.md: Natural language instructions. "Use Riverpod for state management," "Name functions in camelCase"
- settings.json: Structured configuration. Permissions, Hooks, MCP, environment variables, model
If you find yourself writing JSON config in CLAUDE.md, it belongs in settings.json.
Q2: What's the difference between .claude/settings.json and .claude/settings.local.json?
.claude/settings.json is team-shared and should be committed to Git. .claude/settings.local.json is personal and should not be committed.
Add .claude/settings.local.json to .gitignore:
# .gitignore
.claude/settings.local.json
Q3: What happens if permission config is malformed?
No crash. Claude Code ignores unparseable rules while other rules work normally. But use the /permissions command to verify your config matches expectations.
Q4: Can I completely disable a tool?
Yes. Add the tool name to the deny list:
{
"permissions": {
"deny": ["WebFetch", "WebSearch"]
}
}Claude will be completely unable to use these tools.
Q5: What if env variables conflict with system environment variables?
settings.json's env overrides same-named system environment variables. If you don't want to override, simply don't set the same key in env.
Q6: How do hooks merge across multiple scopes?
Hooks follow object merge rules. Hook arrays for the same lifecycle event (e.g., PostToolUse) are concatenated. See the Hooks Guide for detailed merge behavior.
Q7: How do I use session-level settings?
Pass them via startup arguments:
claude --settings /path/to/session-settings.jsonIdeal for CI/CD scenarios where each run uses a different configuration.
Summary
settings.json boils down to three things:
- Permission control — deny/allow/ask as three lines of defense, deny always wins
- Scope layering — five layers from enterprise to personal, allocate config wisely
- Team collaboration — Shared for standards, Local for personal, secrets never in the repo
Master these, and you can let Claude Code operate at full power within safe, controlled boundaries.
Recommended Reading
- Claude Code Advanced Guide — The complete getting-started guide
- The Complete CLAUDE.md Guide — settings.json's partner, managing "what to do"
- Claude Code Hooks Guide — Deep dive into the hooks field in settings.json
- Context Management Guide — Manage context well to make configuration more effective
- Multi-Agent Parallelism Guide — Configuration strategies for multi-terminal collaboration
- Custom Slash Commands Guide — Turn common operations into one-click commands
- MCP Server Deep Dive — Deep dive into the mcpServers field in settings.json
- Effective Prompt Guide — After configuring your environment, writing good prompts is the next step