CCA-F Study Day 13/20: Permissions, Settings & Hooks in Claude Code
Domain 3: Claude Code Configuration & Workflows (~20% of exam)
π Today's Focus
Yesterday you mastered CI/CD integration with the -p flag, batch processing, and multi-pass code review. Today we dive into the permission model, settings.json hierarchy, and hooks in the Claude Code context β the security and governance layer that makes Claude Code enterprise-ready.
This is exam-critical material because the exam tests your ability to design permission boundaries for production environments and distinguish between what should be controlled via prompts vs. what MUST be controlled via deterministic hooks and settings.
π Core Concepts
1. The Settings Scope Hierarchy (Priority Order)
Claude Code uses a multi-layered configuration system where higher-priority scopes override lower ones:
| Priority | Scope | Location | Who It Affects | Shared? |
|---|---|---|---|---|
| 1 (highest) | Managed | Server-managed, MDM/OS policies, or /etc/claude-code/managed-settings.json | All users on machine | Yes (IT-deployed) |
| 2 | CLI arguments | Command-line flags | Current session only | No |
| 3 | Local | .claude/settings.local.json | You, this repo only | No (gitignored) |
| 4 | Project | .claude/settings.json | All collaborators | Yes (committed) |
| 5 (lowest) | User | ~/.claude/settings.json | You, all projects | No |
π Critical exam concept: Permission rules merge across scopes rather than simply overriding. The evaluation order is: deny β ask β allow. Deny always wins.
2. Permission Modes
Claude Code has several permission modes that control how tool execution is gated:
| Mode | Behavior | Use Case |
|---|---|---|
| default | Tools not covered by allow rules trigger approval callback | Interactive development |
| acceptEdits | Auto-approves file edits; Bash follows default rules | Trusted edit workflows |
| plan | Read-only; Claude explores and produces a plan only | Architecture exploration |
| dontAsk | Never prompts; tools pre-approved by rules run, else denied | CI/CD pipelines |
| auto | Model classifier approves/denies each tool call | Balanced autonomy with safety |
| bypassPermissions | All tools run without asking | Isolated sandboxes ONLY |
3. Permission Rule Syntax
Permissions are defined in the permissions object within settings.json using allow, deny, and askarrays:
{
"permissions": {
"allow": [
"Bash(npm run lint)",
"Bash(npm run test *)",
"Read(~/.zshrc)"
],
"deny": [
"Bash(curl *)",
"Read(./.env)",
"Read(./.env.*)",
"Read(./secrets/**)"
]
}
}
Rule syntax patterns:
ToolNameβ matches all uses of a toolToolName(specifier)β matches specific tool invocations*β wildcard within specifiers**β recursive wildcard for directory trees
4. Auto Mode β The Classifier-Based Permission System
Auto mode is Anthropic's middle ground between manual approval and --dangerously-skip-permissions. Key architecture:
- Input layer: Server-side prompt-injection probe scans tool outputs before entering context
- Output layer: A transcript classifier (running Sonnet 4.6) evaluates each action against decision criteria
- Two-stage filter: Fast single-token filter β Chain-of-thought reasoning (only if Stage 1 flags)
- Tier 1: Built-in safe-tool allowlist (reads, searches) β never reaches classifier
- Tier 2: In-project file operations β allowed without classifier
- Tier 3: Everything else (shell commands, web fetches, external tools) β transcript classifier
Performance: 0.4% false positive rate, 17% false negative rate on real overeager actions.
5. Hooks in Claude Code
Hooks are user-defined shell commands that execute at specific lifecycle points. Unlike CLAUDE.mdinstructions (advisory), hooks are deterministic β they guarantee the action happens.
Available Hook Events:
| Event | Triggers On | Use Case |
|---|---|---|
| SessionStart | Session begins (or compaction) | Inject context, load env vars |
| PreToolUse | Before a tool executes | Block dangerous commands, validate inputs |
| PostToolUse | After a tool executes | Auto-format, audit logging |
| Notification | Claude needs input | Desktop alerts, Slack notifications |
| PermissionRequest | Before showing permission dialog | Auto-approve specific patterns |
| ConfigChange | Settings file modified | Audit logging, compliance |
| CwdChanged | Working directory changes | Reload environment variables |
| FileChanged | Watched files change | Hot-reload configs |
Hook Decision Control:
exit 0β Allow the operationexit 2β Block the operation (Claude gets feedback explaining why)- JSON output with
{"hookSpecificOutput": {"decision": ...}}β Fine-grained control
6. Iterative Refinement Strategies
The exam tests your knowledge of how to improve Claude Code output across turns:
- Feedback loops: Provide specific corrections, not just "try again"
- Escalating specificity: Start broad, narrow down as you see output
- Plan-then-execute: Use plan mode to align before committing to changes
- Test-driven refinement: Write tests first, let Claude iterate until they pass
π« Anti-Patterns & Exam Traps
| β Anti-Pattern (Wrong Answer) | β Correct Approach | Why It's Wrong |
|---|---|---|
| Using CLAUDE.mdinstructions to enforce security policies | Use hooks (PreToolUse) with exit code 2 for deterministic enforcement | CLAUDE.md is advisory β the model CAN ignore it. Hooks are deterministic. |
| Using bypassPermissions in production | Use dontAsk with explicit allow/deny rules in CI/CD | bypassPermissions is for isolated sandboxes only β never production |
| Putting secrets in settings.json that's committed to git | Use .claude/settings.local.json(gitignored) or env vars | Project settings are shared; secrets would be exposed to all collaborators |
| Using a single global allow-all rule for convenience | Narrow, specific allow rules per tool pattern | Broad rules bypass the classifier in auto mode and create attack surface |
| Relying on the model's judgment to not edit dangerous files | Use deny rules: "deny": ["Edit(.env)", "Edit(secrets/***)"] | Models can be manipulated via prompt injection; deny rules are absolute |
| Putting machine-specific configs in project settings | Use local scope (settings.local.json) for machine-specific overrides | Project settings are committed to git β different machines have different paths/tools |
β οΈ Key exam trap: The exam will present a scenario where a team needs to "ensure Claude never modifies production configs." The WRONG answer is "add instructions to CLAUDE.md saying 'never modify production configs.'" The RIGHT answer is a PreToolUse hook that blocks writes to those paths with exit 2, OR a deny rule in settings.json.
π» Code Examples
Example 1: Production-Ready settings.json
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"allow": [
"Bash(npm run lint)",
"Bash(npm run test *)",
"Bash(npm run build)",
"Bash(git status)",
"Bash(git diff *)",
"Bash(git log *)",
"Read(src/**)",
"Read(tests/**)",
"Edit(src/**)",
"Edit(tests/**)"
],
"deny": [
"Bash(rm -rf *)",
"Bash(curl *)",
"Bash(wget *)",
"Bash(git push --force *)",
"Read(.env*)",
"Read(secrets/**)",
"Edit(.github/workflows/**)",
"Edit(package-lock.json)",
"Edit(infrastructure/**)"
]
},
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/validate-bash.sh"
}
]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
}
]
}
]
}
}
Example 2: PreToolUse Hook Script (Block Protected Files)
#!/bin/bash
# .claude/hooks/protect-files.sh
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
PROTECTED_PATTERNS=(".env" "package-lock.json" ".git/" "infrastructure/" "secrets/")
for pattern in "${PROTECTED_PATTERNS[@]}"; do
if [[ "$FILE_PATH" == *"$pattern"* ]]; then
echo "Blocked: $FILE_PATH matches protected pattern '$pattern'" >&2
exit 2 # EXIT CODE 2 = BLOCK THE OPERATION
fi
done
exit 0 # Allow the operation
Example 3: Managed Settings for Enterprise
// /etc/claude-code/managed-settings.json
// Deployed by IT β CANNOT be overridden by users
{
"permissions": {
"deny": [
"Bash(rm -rf /)",
"Bash(* --force *)",
"Bash(ssh *)",
"Bash(scp *)",
"Edit(/etc/**)",
"Edit(/var/**)"
]
},
"allowManagedPermissionRulesOnly": true,
"allowManagedHooksOnly": true,
"allowManagedMcpServersOnly": true
}
Example 4: Auto-Format + Re-inject Context After Compaction
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
}
]
}
],
"SessionStart": [
{
"matcher": "compact",
"hooks": [
{
"type": "command",
"command": "echo 'Reminder: Use TypeScript strict mode. Run bun test before committing. Current sprint: auth refactor. Do not modify .env files.'"
}
]
}
]
}
}
π¬ Video / Webinar to Watch
Claude Code Advanced Patterns: Subagents, MCP, and Scaling to Real Codebases β Anthropic webinar (March 2026) by Lizzie Alvarado Ford and Alon Krifcher from Applied AI at Anthropic. Covers subagents and hooks for orchestrating multi-step work, permission guardrails, and CI pipeline integration. Most relevant section: How hooks turn Claude Code into an orchestrator β enforcing guardrails deterministically.
Also read: How We Built Claude Code Auto Mode: A Safer Way to Skip Permissions β Anthropic engineering blog post (March 2026) explaining the classifier architecture, threat model, and performance metrics behind auto mode.
π Reading
- Primary: Claude Code Permissions Documentation
- Secondary: Automate Workflows with Hooks
- Reference: Claude Code Settings & Configuration
- Deep dive: Hooks Reference (full event schemas)
π οΈ Hands-On Exercise (20β30 minutes)
Design a complete security-hardened settings.json for a financial services team:
- Create a
.claude/settings.json(project scope) that:- Allows Claude to read/edit only
src/andtests/ - Denies access to
.env*,secrets/,infrastructure/, and*.pem - Allows only specific npm commands (lint, test, build)
- Blocks all network commands (curl, wget, ssh, scp)
- Allows Claude to read/edit only
- Create a
PreToolUsehook script that:- Reads the tool input JSON from stdin
- Checks if a Bash command contains any production database connection strings
- Exits with code 2 and a clear message if blocked
- Create a
.claude/settings.local.jsonwith your personal overrides:- Allow reading your personal
~/.aws/configfor reference - Set your preferred model
- Allow reading your personal
- Bonus: Write a
PostToolUsehook that appends an audit log entry (timestamp, tool name, file path) to.claude/audit.logfor every file edit
π Quick Quiz
Q1: A team wants to ensure Claude Code never modifies their CI/CD pipeline configuration files. Which approach provides the MOST reliable enforcement?
A) Add "Never modify files in .github/workflows/" to the project CLAUDE.md
B) Add "deny": ["Edit(.github/workflows/***)"] to project settings.json
C) Use plan mode so Claude only suggests changes
D) Set permission mode to "acceptEdits" with a restricted directory list
Q2: In Claude Code's auto mode, which actions NEVER reach the transcript classifier?
A) Shell commands that modify the filesystem outside the project directory B) File reads, text searches, and code navigation within the project C) Web fetches to external APIs D) MCP tool calls to connected services
Q3: A developer needs machine-specific API endpoint configurations that shouldn't be shared with teammates. Where should they put these settings?
A) ~/.claude/settings.json (user scope)
B) .claude/settings.json (project scope)
C) .claude/settings.local.json (local scope)
D) CLAUDE.local.md in the project root
Answers
Q1: B β Deny rules in settings.json are deterministic and cannot be overridden by the model. CLAUDE.md(A) is advisory. Plan mode (C) prevents execution but doesn't block when switching out of plan mode. acceptEdits (D) only controls the edit approval flow, not which files can be targeted.
Q2: B β File reads, text searches (Grep), and code navigation are on the built-in safe-tool allowlist (Tier 1). They can never modify state, so they bypass the classifier entirely. Shell commands outside the project (A), web fetches (C), and MCP tools (D) all reach Tier 3 (the classifier).
Q3: C β .claude/settings.local.json is gitignored and scoped to this project only. User scope (A) would apply to ALL projects. Project scope (B) would be committed and shared. CLAUDE.local.md (D) is for instructions, not structured settings.
π Tomorrow's Preview
Tomorrow we move to Domain 4: Explicit Criteria & Few-Shot Prompting β the prompt engineering fundamentals that underpin structured output design, including XML tag patterns and chain-of-thought elicitation.