Study guide

12.2 Permissions, Modes & Hooks

Claude Certified Developer - Foundations CCDV-F

Track your progress as you read

Finished sections get a check mark.

1 Chapter 1: Core Messages API & Streaming

1.1 Messages API Fundamentals & Request Lifecycle 1.2 Streaming & Server-Sent Events (SSE) 1.3 Thinking: Adaptive Mode, Effort & Reasoning Budgets

2 Chapter 2: Optimization & Asynchronous Batches

2.1 Prompt Caching Mechanics & Breakpoints 2.2 Message Batches API & Asynchronous Workloads

3 Chapter 3: Structured Outputs, Documents & Files API

3.1 Structured Outputs & JSON Extraction 3.2 Files API, Document Blocks & Citations

4 Chapter 4: Application Design, Resilience & Delivery Life Cycle

4.1 API Resilience, Error Handling & Retries 4.2 Production Backend Architecture & State Management 4.3 Requirements Analysis & Claude Application Design 4.4 The Claude Application Life Cycle & Configuration Management

5 Chapter 5: LLM Fundamentals, Model Selection & Versioning

5.1 LLM & Technical Fundamentals: Tokens, Context Windows & Sampling 5.2 Claude Model Family Selection 5.3 Model Versioning, Snapshot Pinning & Migration 5.4 Latency & Throughput Optimization

6 Chapter 6: Cost Engineering & Context Management

6.1 Production Cost Optimization Strategies 6.2 Context Window Management & Compaction

7 Chapter 7: Foundations of Workflows & Deterministic Patterns

7.1 Agent vs. Workflow Architectural Foundations 7.2 Workflow Patterns: Prompt Chaining & Routing 7.3 Workflow Patterns: Parallelization & Voting

8 Chapter 8: Autonomous Agents & Multi-Step Systems

8.1 Workflow Patterns: Orchestrator-Workers & Evaluator-Optimizer 8.2 Agent Loop Implementation & State Tracking 8.3 Agent Frameworks & Human-in-the-Loop (HITL)

9 Chapter 9: Prompt and Context Engineering

9.1 System Prompt Design & Role Framing 9.2 XML Tags & Structural Prompt Delimiters 9.3 Few-Shot Prompting & In-Context Learning 9.4 RAG Context Injection & Document Formatting

10 Chapter 10: Tool Calling & Model Context Protocol (MCP)

10.1 Tool Schema Design & Parameter Validation 10.2 Tool Execution, Multi-Tool Calls & Error Handling 10.3 MCP Architecture & Core Primitives 10.4 MCP Transports & Client/Server Integration 10.5 Agentic Customization: Built-in Tools, Custom Tools, Skills & MCP

11 Chapter 11: Security, Safety & Governance

11.1 Direct & Indirect Prompt Injection Defenses 11.2 Tool Permissions, Sandboxing & Least Privilege 11.3 Data Privacy, Redaction & Audit Logging 11.4 Identity, Secrets & API Key Management

12 Chapter 12: Claude Code Developer Tooling

12.1 Claude Code Architecture & Project Configuration 12.2 Permissions, Modes & Hooks

13 Chapter 13: Evaluation, Testing & Production Observability

13.1 Evaluation Design & Golden Datasets 13.2 Production Observability, Tracing & Debugging

12.2 Permissions, Modes & Hooks

Key Takeaways Explain This Topic AI

Permission rules take the form Tool or Tool(specifier), such as Bash(pnpm test:*) or Read(./.env), and live in the permissions object's allow, ask, and deny arrays.

Permission modes are default (aliased manual), acceptEdits, plan, auto, dontAsk, and bypassPermissions; dontAsk is the strictest because it auto-denies anything not pre-approved.

Hook events are PascalCase and specific - PreToolUse, PostToolUse, SessionStart, SubagentStop, PreCompact - and are configured three levels deep as event, then matcher group, then handler.

Only exit code 2 blocks a hooked action; exit 0 succeeds and other non-zero codes are non-blocking errors that let the action proceed unless decision JSON says otherwise.

Headless runs use claude --print with --output-format, --permission-mode, --allowedTools, and --max-turns; --dangerously-skip-permissions is equivalent to --permission-mode bypassPermissions.

Last updated: October 2026

Permissions, Modes & Hooks

Exam Blueprint Focus: This section carries two separately weighted sub-skills: Claude Code Operation (3.1%) and, inside the Security and Safety domain, Claude Hooks (1.0%). Both are graded on exact names — the permission rule syntax, the mode names, and the hook event names . Approximate answers are wrong answers here.

The Permission Problem

An autonomous coding agent with shell and filesystem access creates a direct tension:

Confirm every read and the agent is unusable for routine work.

Confirm nothing and one misread instruction can drop a database, overwrite uncommitted work, or force-push to main .

Claude Code resolves this with a tiered permission system plus explicit rules you write.

  • Tool type Example Approval required in Manual mode
  • Read-only Read , Grep , Glob No, within the working directory and additionalDirectories
  • Bash commands Shell execution Yes, except a built-in set of read-only commands
  • File modification Edit , Write Yes, until the session ends or a rule allows it
  • Permission Rule Syntax

Every rule has the form Tool or Tool(specifier) . Parentheses inside a specifier are literal and need no escaping.

  • Rule Effect
  • Bash All Bash commands ( Bash(*) is equivalent)
  • Bash(npm run build) Exactly that command
  • Bash(pnpm test:*) Any command matching the prefix pattern
  • Read(./.env) Reading .env in the current directory
  • Edit(src/**) Edits under src/
  • WebFetch(domain:example.com) Fetches to that domain
  • mcp__github__create_issue One tool from an MCP server
  • Agent(model:opus) Subagent calls requesting the Opus tier
  • Rules live in three arrays under permissions : {
  • "permissions": {
  • "allow": ["Bash(pnpm test:*)", "Read(src/**)"],
  • "ask": ["Bash(git push:*)"],
  • "deny": ["Read(./.env)", "Bash(curl:*)"],
  • "defaultMode": "acceptEdits",
  • "additionalDirectories": ["../shared-lib"]
  • }
  • }

Behaviour that is directly testable:

deny and ask rules apply immediately. allow rules and additionalDirectories wait until the folder is trusted.

An allow rule in your local file does not outrank an ask rule from project or managed settings. This is why "Yes, and don't ask again" sometimes still prompts.

List keys merge across scopes. Your personal allow entries add to the team's; they do not replace them.

A deny rule with no specifier removes the tool from Claude's context entirely , so the model does not even see it as an option.

High-yield trap: allowedTools , confirmTools , and blockedCommands are not settings.json keys. --allowedTools and --disallowedTools exist as CLI flags , but the file-based configuration is the permissions object only.

Permission Modes

The mode sets the baseline behaviour before rules are consulted. Set it with permissions.defaultMode in settings, or --permission-mode for one session.

  • Mode Behaviour
  • default (aliased manual ) Prompts on first use of each tool

acceptEdits Auto-accepts file edits and common filesystem commands ( mkdir , touch , mv , cp ) inside the working and additional directories

  • plan Claude explores with reads and read-only shell commands but does not edit source files
  • auto Auto-approves tool calls with background safety classification instead of human review
  • dontAsk Auto- denies anything not pre-approved by an allow rule
  • bypassPermissions Skips prompts entirely, except for the small set of actions no mode auto-approves

Two nuances worth carrying into the exam. dontAsk is not a permissive mode — it is the strictest, failing closed rather than prompting, which makes it the right choice for unattended automation with a curated allowlist. And bypassPermissions is the dangerous one: it skips prompts even for writes to protected paths such as .git and .claude , so it belongs only in a container or VM. Organizations can forbid it with permissions.disableBypassPermissionsMode (and disableAutoMode for auto ) in managed settings, where individuals cannot override it.

  • Interactive vs. Headless Operation
  • Interactive mode

The default when you run claude in a terminal. You see a syntax-highlighted unified diff before an Edit applies, the exact command string before a Bash call runs, and an approval prompt offering yes / no / "yes, and don't ask again" / abort.

Headless mode

Headless runs are driven by --print ( -p ) , not by an --auto-pilot flag: claude -p "Fix the failing tests in src/billing and commit the result" \

  • -output-format json \
  • -permission-mode dontAsk \
  • -allowedTools "Read" "Edit" "Bash(pnpm test:*)" \
  • -model claude-sonnet-5-5 \
  • -max-turns 8

--output-format takes text , json , or stream-json — use json when a CI step needs to parse the result.

--max-turns caps the agentic loop, the primary defence against a runaway agent burning budget.

--dangerously-skip-permissions is equivalent to --permission-mode bypassPermissions . The name is the warning.

Because nobody is watching, headless deployments need three safeguards: an ephemeral sandbox (container or VM, no host credentials), a scoped allowlist paired with dontAsk so anything unlisted fails closed, and fail-fast budgets via --max-turns and timeouts.

Hooks: Deterministic Lifecycle Governance

Prompts steer a probabilistic model. Hooks execute regardless of what the model decides , which is what makes them the enforcement layer that CLAUDE.md is not.

Event names

Hooks are keyed by event name in settings.json . The names are PascalCase and specific — PreToolUse , not preTool . The events you should know:

  • Event Fires
  • SessionStart A session begins or resumes
  • UserPromptSubmit Before Claude processes a prompt
  • PreToolUse Before a tool call executes
  • PermissionRequest When a tool needs a permission decision
  • PostToolUse After a tool call succeeds
  • PostToolUseFailure After a tool call fails
  • SubagentStart / SubagentStop A subagent is spawned / finishes
  • PreCompact / PostCompact Around context compaction
  • Stop Claude finishes responding
  • SessionEnd The session ends
  • Configuration shape
  • The structure is three levels deep : event → matcher group → handler. {
  • "hooks": {
  • "PreToolUse": [
  • {
  • "matcher": "Bash",
  • "hooks": [
  • {
  • "type": "command",
  • "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/guard.sh",
  • "timeout": 30
  • }
  • ]
  • }
  • ],
  • "PostToolUse": [
  • {
  • "matcher": "Edit|Write",
  • "hooks": [
  • { "type": "command", "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/format.sh" }
  • ]
  • }
  • ]
  • }
  • }

A matcher may be an exact tool name, a pipe- or comma-separated list, or a regex such as mcp__.* . Omitting it (or using "*" ) matches everything. Besides type: "command" , handlers can be http , mcp_tool , prompt , or agent .

Hook input and the exit-code contract

The handler receives a JSON payload on stdin . Tool events carry tool_name , tool_input , and tool_use_id , alongside common fields such as session_id , cwd , and permission_mode .

The exit-code semantics are precise, and "any non-zero blocks" is wrong :

Exit 0 — success. If stdout is a JSON object it is parsed for decision fields; otherwise it is treated as text.

Exit 2 — blocking error. The action is blocked on events that support blocking, with the reason taken from JSON or stderr.

Other non-zero codes — a non-blocking error. The action proceeds unless valid decision JSON says otherwise.

  • The explicit, reliable way to block is the JSON decision object: #!/usr/bin/env bash
  • set -euo pipefail
  • PAYLOAD=$(cat)
  • TOOL=$(echo "$PAYLOAD" | jq -r '.tool_name // empty')
  • if [[ "$TOOL" == "Bash" ]]; then
  • CMD=$(echo "$PAYLOAD" | jq -r '.tool_input.command // empty')
  • BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo unknown)
  • if [[ "$BRANCH" == "main" && "$CMD" == git\ commit* ]]; then
  • jq -n '{hookSpecificOutput: {hookEventName: "PreToolUse",
  • permissionDecision: "deny",
  • permissionDecisionReason: "Commits on main are blocked; branch first."}}'
  • exit 0
  • fi
  • if echo "$CMD" | grep -iqE '(api[_-]?key|password|secret|bearer)'; then
  • echo "Command may expose a credential." >&2
  • exit 2
  • fi
  • fi
  • exit 0

permissionDecision accepts "allow" , "deny" , "ask" , or "defer" ( "defer" ends the turn so the tool call can be resumed later, and only works in non-interactive -p runs); deny and ask rules in settings are still evaluated whatever the hook returns. A deny reason is fed back to Claude as tool feedback, so it can choose a different approach — which is why a blocking hook should always explain itself. A hook that blocks silently sends the model into a retry loop that burns tokens on the same rejected action.

Post-tool hooks

PostToolUse fires after a tool succeeds and is where formatters and linters belong: run Prettier, Black, or gofmt on the file that was just written, so what lands on disk is always formatted. Note the ordering hazard — if a PostToolUse hook reformats a file and shifts line numbers, a subsequent Edit built on stale offsets can fail or corrupt adjacent lines.

Skills, Slash Commands, and Subagents

Three extension mechanisms sit alongside permissions and hooks, and CCDV-F expects you to tell them apart.

Skills are reusable procedures in SKILL.md files under .claude/skills/<name>/ (project) or ~/.claude/skills/<name>/ (personal). They use progressive disclosure : only the description sits in context, and the body loads when the skill is invoked — automatically when the description matches, or manually as /skill-name .

Slash commands invoke a fixed action by name, including user-invocable skills.

Subagents are defined in .claude/agents/ and run a delegated task in an isolated context window .

Why subagents matter architecturally +-------------------------------------------------------------------------+

  • | Primary Orchestrator Agent |
  • | Task: "Refactor all deprecated auth endpoints" |
  • | Context: clean, high-level architecture only |
  • +------------------------------------+------------------------------------+
  • | spawns subagent
  • v
  • +------------------------------------+------------------------------------+
  • | Search Subagent (isolated context) |
  • | Scope: find all 24 usages of verifyAuthToken() |
  • | Tools: Read, Glob, Grep only - no Bash, no Edit |
  • | Reads 500 files, processes 15,000 lines |
  • +------------------------------------+------------------------------------+
  • | returns summary only
  • v
  • | Orchestrator receives: "3 files need updates: a.ts, b.ts, c.ts" |
  • | The 15,000 lines of raw output never enter the parent context |
  • +-------------------------------------------------------------------------+

Context hygiene. Raw search output stays quarantined in the subagent; only the synthesis returns.

Least privilege. A read-only exploration subagent gets Read , Glob , and Grep and nothing else.

Parallelism. Independent subproblems investigate concurrently.

A skill with context: fork in its frontmatter combines both: skill modularity plus subagent isolation.

Common Traps

Using lowercase hook names. preTool and postTool are not events. The names are PreToolUse and PostToolUse .

Assuming any non-zero exit blocks. Only exit 2 blocks. Exit 1 or 3 is a non-blocking error and the action proceeds.

Blocking without a reason. Emit permissionDecisionReason , or write to stderr, so the model can adapt instead of retrying.

Unrestricted bypassPermissions on a real workstation. It skips prompts even for .git and .claude writes. Containers only.

Confusing dontAsk with a permissive mode. It auto- denies anything not pre-approved — the safest headless default, not the loosest.

Over-privileged subagents. Exploration subagents should be read-only unless the delegated task genuinely requires writes.

Putting a procedure in CLAUDE.md instead of a Skill. CLAUDE.md loads every session; a Skill loads only when invoked.

Loading diagram... Permission and Hook Evaluation Order for a Tool Call