AI

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 modelsettings.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 β†’ allowDeny 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 allowdeny, 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 tool
  • ToolName(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 operation
  • exit 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


πŸ› οΈ Hands-On Exercise (20–30 minutes)

Design a complete security-hardened settings.json for a financial services team:

  1. Create a .claude/settings.json (project scope) that: 
    • Allows Claude to read/edit only src/ and tests/
    • Denies access to .env*secrets/infrastructure/, and *.pem
    • Allows only specific npm commands (lint, test, build)
    • Blocks all network commands (curl, wget, ssh, scp)
  2. Create a PreToolUse hook 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
  3. Create a .claude/settings.local.json with your personal overrides: 
    • Allow reading your personal ~/.aws/config for reference
    • Set your preferred model
  4. Bonus: Write a PostToolUse hook that appends an audit log entry (timestamp, tool name, file path) to .claude/audit.log for 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.