Claude Code Settings
Scope: Settings file layout + scope precedence, and a convention-validator
hook recipe. NOT a hooks or permissions deep-dive — those live elsewhere.
Load if: Editing settings.json / .claude config, choosing which scope a
key belongs in, OR building a hook that enforces a repo convention.
Prerequisites: @smith-ctx-claude/SKILL.md (hooks + permission-mode
deep-dive), @smith-ctx-claude-mode-auto/SKILL.md (permissions, $defaults, classifier)
Authoritative source: Claude Code settings, hooks (verified 2026-06-25)
CRITICAL: One Key, One Scope
- Put a key in the RIGHT file: shared team config → committed
.claude/settings.json; personal/secret/machine-specific → gitignored.claude/settings.local.json; cross-project defaults →~/.claude/settings.json. - A key set in a higher-precedence scope overrides the same key lower down; omitting it leaves the lower value in place (keys merge, they don't wipe).
permissions.defaultMode: "auto"is honored ONLY in~/.claude/settings.json— it is ignored in project/local settings, so a repo can't grant itself auto.- Cross-reference
@smith-ctx-claude/SKILL.mdand@smith-ctx-claude-mode-auto/SKILL.mdfor hook events/handlers or permission rules instead of re-documenting them here (DRY). - Keep secrets and personal
defaultModeoverrides in.local.json—.claude/settings.jsonships to every teammate.
Settings files & precedence
Four scopes, highest precedence first (a managed policy can't be overridden):
- Managed policy settings — org/MDM deployed; absolute.
.claude/settings.local.json— your personal, gitignored project overrides..claude/settings.json— committed, shared project config.~/.claude/settings.json— your cross-project user defaults.
Command-line --settings <file-or-json> merges as a temporary top layer.
The same scope names appear as ConfigChange hook matchers (user_settings,
project_settings, local_settings, policy_settings, skills) — use that
event to audit or block settings changes.
Convention-validator hook recipe
A PreToolUse hook can ENFORCE a repo convention by rejecting the offending
tool call (exit 2 blocks it and shows stderr to Claude). The if field
narrows the handler to just the calls you care about, using permission-rule
syntax. Example: block any git commit made on a protected branch — enforcing
the smith "never commit to main/master/develop" rule mechanically.
In committed .claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-protected-commit.sh",
"args": [],
"if": "Bash(git commit *)"
}
]
}
]
}
}
args is set (to []) because command uses the ${CLAUDE_PROJECT_DIR} path
placeholder — that selects exec form, where the placeholder is substituted and
no shell re-tokenizes the path.
In .claude/hooks/block-protected-commit.sh (chmod +x):
#!/usr/bin/env bash
set -euo pipefail
branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
case "$branch" in
main|master|develop)
echo "Blocked: commit on protected branch '$branch'. Use a feature branch." >&2
exit 2
;;
esac
exit 0
Generalize the same shape: swap the if pattern and script to validate branch
names (Bash(git checkout -b *)), file paths (Edit(*)), or a commit-message
format. For richer control than exit 2, a PreToolUse hook can return
hookSpecificOutput.permissionDecision: "deny" with a reason — see
@smith-ctx-claude/SKILL.md.
Related
@smith-ctx-claude/SKILL.md- Hook events/handlers, permission modes (deep-dive)@smith-ctx-claude-mode-auto/SKILL.md- Permissions rules,$defaults, classifier lists@smith-dev/SKILL.md- Pre-commit checks this hook can enforce@smith-git/SKILL.md- The protected-branch rule the recipe enforces
Before You Finish
Placing a key:
- Shared team behavior → committed
.claude/settings.json - Personal/secret/machine-specific → gitignored
.claude/settings.local.json - Cross-project default →
~/.claude/settings.json(only home forauto)
Building a convention validator:
PreToolUse+matcher: "Bash"+ anifpermission-rule to scope it- Script exits 2 to block (stderr is shown to Claude); use exec form (
args) whencommandreferences a${CLAUDE_PROJECT_DIR}path placeholder