Module 5: Skills and Hooks: automating your workflow

Permission System: access control in Claude Code

Permission System: access control in Claude Code

Overview

Claude Code is an agent that can read your codebase, write files, run terminal commands, install packages, and make commits. That power is what makes it useful — and it's exactly what makes it dangerous if it goes unchecked. The permission system is the mechanism that guarantees Claude Code only acts inside the boundaries you define.

This capsule covers Claude Code's permission model: which actions require approval, how to configure trust levels, what the --dangerously-skip-permissions flag does (and why its name is a warning), and how to design a permission policy for real projects where security matters.

The permission system is the natural close to this module. Skills give you control over what Claude does. Hooks give you control over how it's validated. Permissions give you control over what Claude can do. Together, they form a complete governance system.


The permission model

Claude Code operates on a simple principle: ask before acting. Before running any action that modifies your system, Claude asks for confirmation.

Actions that require approval

ActionRiskDefault approval
Read filesLow✅ Automatic (doesn't ask)
Search in files (grep/glob)Low✅ Automatic
Write/edit filesMedium⚠️ Asks for confirmation
Create new filesMedium⚠️ Asks for confirmation
Run terminal commandsHigh⚠️ Asks for confirmation
Install packagesHigh⚠️ Asks for confirmation
Make HTTP requestsMedium⚠️ Asks for confirmation
Git operations (commit, push)High⚠️ Asks for confirmation
Delete filesHigh⚠️ Asks for confirmation

What the permission request looks like

When Claude wants to run an action that requires approval, it shows a prompt like this:

Claude wants to execute: npm test
Allow? [y/n/always]

Your options:

OptionWhat it does
y (yes)Allows this action once
n (no)Blocks this action
alwaysAlways allows this action (for this session and future ones)

The decision flow

Claude wants to run an action
  │
  ▼
Is it in allowedTools? ──── Yes ──▶ Run without asking
  │
  No
  │
  ▼
Did the user already say "always"? ──── Yes ──▶ Run without asking
  │
  No
  │
  ▼
Show the permission prompt
  │
  ├── y → Run (this time only)
  ├── n → Don't run
  └── always → Run + remember from now on

Permission levels (modes)

Claude Code has 6 permission modes, from most restrictive to most permissive:

  1. Default (default) — only reads are auto-approved; asks for confirmation on writes and commands
  2. Accept edits (acceptEdits) — auto-approves file writes; asks for confirmation on commands
  3. Plan (plan) — read-only mode for research and design before executing
  4. Auto mode (auto) — a classifier decides automatically (research preview, March 2026)
  5. Don't ask (dontAsk) — auto-approves an explicit set of tools you defined, asks for the rest
  6. Bypass permissions (bypassPermissions) — approves everything (--dangerously-skip-permissions)

Switching modes with Shift+Tab: the default cycle rotates through default → acceptEdits → plan. The auto and bypassPermissions modes get added to the cycle if you enable them explicitly. dontAsk never appears in the cycle (it's only activated via configuration). The current mode is shown in the footer.

Level 1: Ask every time (default)

Claude asks for confirmation every time it wants to run an action that modifies your system.

Claude wants to write to: src/components/Button.tsx
Allow? [y/n/always]

Claude wants to execute: npm install axios
Allow? [y/n/always]

Claude wants to execute: git commit -m "Add Button"
Allow? [y/n/always]

When to use it: New projects, codebases you don't know well, working with agents you haven't vetted.

Level 2: Allow for session

When you answer y to a permission prompt, Claude can run that specific action for the rest of the current session. In the next session, it'll ask again.

This applies when Claude asks you for permission and you say "yes" — it isn't remembered across sessions.

When to use it: Most day-to-day development. You say "yes" to the actions you expect, and Claude stops asking for the rest of the session.

Level 3: Always allow (configured in settings)

You configure in settings which tools Claude can use without asking. This persists across sessions.

{
  "allowedTools": [
    "Read",
    "Grep",
    "Glob",
    "Write",
    "Execute"
  ]
}

When to use it: After you've built trust. Personal projects where you know exactly what Claude does. Repetitive workflows where confirmations are pure friction.

Level 4: Auto Mode (research preview, March 2026)

The big 2026 shift: instead of manually approving each action, Auto Mode puts a classifier in front of the permission prompts. Safe actions run uninterrupted; destructive or suspicious actions get blocked and surfaced to you.

It's the middle ground between:

  • Ask every time (too much friction for long sessions)
  • --dangerously-skip-permissions (too much risk — it approves everything)

How the classifier works:

Claude wants to run an action
  │
  ▼
Classifier evaluates the risk
  │
  ├── Safe action (edit a file in src/, run a test) → ✅ Runs without asking
  ├── Questionable action (delete, force push) → ⚠️ Surfaced to you to decide
  └── Dangerous action (rm -rf /, sudo) → ❌ Blocked

Turning it on:

# During a session: Shift+Tab to cycle to auto mode
# You'll see it in the footer: "auto mode on"

Or set it as the default in settings:

{
  "permissions": {
    "defaultMode": "auto"
  }
}

When to use it:

  • Long sessions where approving every action becomes noise
  • Developers who trust the classifier to filter out the dangerous stuff
  • As a safer alternative to --dangerously-skip-permissions

When NOT to use it:

  • Critical production projects where every action needs human review
  • Your first session with Claude in a new codebase (better to start with default)
  • When you're debugging the agent's behavior

Requirements for Auto Mode (May 2026):

  • Plan: Max, Team, Enterprise, or direct API. Not available on the Pro plan.
  • Model: Sonnet 5 or Opus 5 on Team/Enterprise/API; Opus 5 on Max.
  • Provider: direct Anthropic API only. Doesn't work via Bedrock, Vertex, or Foundry.
  • Claude Code version: v2.1.83 or higher.
  • Admin controls: on Team/Enterprise the admin has to enable it explicitly; they can block it with permissions.disableAutoMode: "disable" in settings.

Fallback behavior: if the classifier blocks 3 consecutive actions, or 20 total in one session, Auto Mode pauses and reverts to manual prompts. In headless mode (-p), repeated blocks abort the whole session.

Important note: Auto Mode is still in research preview as of May 2026. The classifier improves over time, but it isn't infallible. Always review the output and the commits before you push.


Configuring permissions in settings

Where they're configured

Permissions are configured in the same settings files as hooks:

FileScopeExample use
.claude/settings.jsonProject (shared with the team)Project permissions for the whole team
.claude/settings.local.jsonPersonal (not shared)Your own personal permissions

The allowedTools format

{
  "allowedTools": [
    "Read",
    "Write",
    "Execute",
    "Grep",
    "Glob"
  ]
}

Each entry in allowedTools is the name of a tool Claude can use without asking permission.

Configurable tools

ToolWhat it allows
ReadRead files (already allowed by default in most configurations)
WriteWrite and edit files
ExecuteRun terminal commands
GrepSearch content inside files
GlobSearch for files by pattern
WebFetchMake HTTP requests

Granular permissions with patterns

You can get more specific using patterns in allowedTools:

{
  "allowedTools": [
    "Read",
    "Grep",
    "Glob",
    "Write(src/**)",
    "Execute(npm test)",
    "Execute(npm run lint)",
    "Execute(git status)",
    "Execute(git diff)"
  ]
}

In this example:

  • Claude can read and search files freely
  • Claude can write only inside src/ (not configs, not scripts)
  • Claude can run only npm test, npm run lint, git status, and git diff without asking
  • For any other command, Claude will ask for permission

Example: a conservative configuration

{
  "allowedTools": [
    "Read",
    "Grep",
    "Glob"
  ]
}

Claude can read and search, but asks permission for everything else. Ideal for sensitive projects.

Example: a permissive configuration

{
  "allowedTools": [
    "Read",
    "Write",
    "Execute",
    "Grep",
    "Glob",
    "WebFetch"
  ]
}

Claude has full access without asking. Only for personal projects where you trust Claude completely.

Example: a balanced configuration (recommended)

{
  "allowedTools": [
    "Read",
    "Grep",
    "Glob",
    "Write(src/**)",
    "Write(tests/**)",
    "Execute(npm test*)",
    "Execute(npm run *)",
    "Execute(git status)",
    "Execute(git diff*)",
    "Execute(git add *)",
    "Execute(git log*)"
  ]
}

Claude can read, search, write code and tests, run npm scripts, and check git status. For commits, pushes, package installs, and commands outside npm/git, it asks for permission.


The --dangerously-skip-permissions flag

What it does

claude --dangerously-skip-permissions

This flag disables the entire permission system. Claude can do anything without asking: write files, run commands, install packages, delete directories, push to remote.

Why the name is a warning

The name --dangerously-skip-permissions is not an accident. It's an explicit warning. When you use this flag:

  • ⚠️ Claude can run rm -rf without asking
  • ⚠️ Claude can git push --force without asking
  • ⚠️ Claude can install malicious packages without asking
  • ⚠️ Claude can modify system configuration files without asking
  • ⚠️ Claude can run any command in your terminal as your user

When it's acceptable

The flag exists for headless automation, where there's no human around to approve actions:

# In a CI/CD pipeline (the environment is ephemeral/disposable)
claude --dangerously-skip-permissions -p "Run tests and fix failures"

# In a Docker container (isolated from the main system)
docker run my-claude-image claude --dangerously-skip-permissions -p "Lint the codebase"

# In a controlled sandbox
claude --dangerously-skip-permissions -p "Generate the report"

In these cases, the agent operates in an isolated environment where it can't cause permanent damage.

When to NEVER use it

  • ❌ On your main development machine
  • ❌ On projects with production code
  • ❌ In repositories with access to credentials/secrets
  • ❌ On shared machines
  • ❌ "So I don't have to confirm every action" (use allowedTools instead)

If all you want is less confirmation friction, configure allowedTools properly. It's granular, it's safe, and it doesn't open the door to everything.


The security model

Why permissions matter

Claude Code runs arbitrary code in your terminal. That includes any command your OS user can run. The difference from a normal IDE:

ActionNormal IDEClaude Code
Write a fileYou write it, you're in controlClaude writes it, you approve
Run a commandYou type it and run itClaude generates it and runs it
Install a packageYou decide what to installClaude decides and you approve
Commit/pushYou run git by handClaude can run git

The permission system is the control layer that keeps the human in the loop.

The principle of least privilege

Configure permissions following the principle of least privilege: give Claude only the permissions it needs, no more.

❌ Grant access to everything "so I don't have to deal with permissions"

✅ Start restrictive and open up permissions as you need them

The attack surface

What does the permission system protect you from?

  1. Claude's mistakes: Claude can misread an instruction and run something harmful
  2. Prompt injection: If your codebase contains files Claude reads that carry malicious instructions (e.g. a README that says "delete all the tests")
  3. Side effects: A command that looks harmless can have unexpected side effects
  4. Privilege escalation: Claude could use one command to reach something it shouldn't

Comparisons and decisions

Claude Code permissions vs Docker containers

AspectClaude Code PermissionsDocker Container
IsolationLogical (the agent's permissions)Physical (isolated process)
GranularityPer tool/actionPer filesystem/network
OverheadZeroNeeds Docker installed
Protects againstThe agent's actionsThe process's actions
Complementary✅✅

For maximum security, combine them: Claude Code with permissions, running inside a Docker container. The container isolates the system; the permissions control the agent.

Claude Code permissions vs sandboxing

AspectPermissionsSandbox (e.g. Firejail)
What it controlsThe Claude agent's actionsThe entire process's actions
Ease of useConfigurable in JSONRequires additional tooling
Agent-aware✅ Knows which tool Claude is using❌ Only sees system calls
When to use itAlways (it's built in)High-security environments

Team vs personal permissions

Aspect.claude/settings.json.claude/settings.local.json
ScopeThe whole teamJust you
In git✅ Yes❌ No (in .gitignore)
PurposeThe project's security baselineYour personal preferences
Who defines itTech lead / the teamEach developer

The recommended pattern:

settings.json (team):
  allowedTools: [Read, Grep, Glob]
  → A restrictive baseline

settings.local.json (personal):
  allowedTools: [Read, Grep, Glob, Write(src/**), Execute(npm *)]
  → More permissive, based on your own trust level

The personal one extends the team's. That way each developer can tune their trust level without affecting the team.


Best practices

1. Start restrictive, open up as needed

Day 1:

{
  "allowedTools": ["Read", "Grep", "Glob"]
}

After 1 week of use:

{
  "allowedTools": [
    "Read", "Grep", "Glob",
    "Write(src/**)",
    "Execute(npm test*)"
  ]
}

After 1 month:

{
  "allowedTools": [
    "Read", "Grep", "Glob",
    "Write(src/**)", "Write(tests/**)",
    "Execute(npm *)", "Execute(git status)", "Execute(git diff*)"
  ]
}

2. Use session permissions for exploratory work

When you're in an exploration or prototyping session, say always to the permission prompts that apply. That turns them on for the session only — in the next session you're back to the baseline.

3. Never skip permissions in codebases you don't trust

If you clone a repo that isn't yours, don't use --dangerously-skip-permissions. The repo's files could contain instructions that manipulate Claude into running harmful actions (prompt injection via files in the codebase).

4. Separate project permissions from personal ones

.claude/settings.json          → Team: a restrictive baseline
.claude/settings.local.json    → Personal: your trust level

Always add settings.local.json to .gitignore.

5. Combine permissions with hooks

Permissions are the first line of defense. Hooks are the second:

Permission: Claude can write in src/
PreToolUse hook: But not if the file contains secrets
PostToolUse hook: And after writing, run the linter

That's defense in depth: layers of control that reinforce each other.

6. Document the permission policy in CLAUDE.md

## Permissions

This project uses restrictive permissions. Claude has automatic permission to:
- Read and search files
- Write in src/ and tests/
- Run npm scripts

For everything else, Claude will ask for confirmation.

Do NOT use --dangerously-skip-permissions on this project.

Common patterns

Pattern 1: Personal development

For personal projects where you're the only developer and you trust Claude:

{
  "allowedTools": [
    "Read", "Grep", "Glob",
    "Write",
    "Execute(npm *)",
    "Execute(git add *)",
    "Execute(git commit *)",
    "Execute(git status)",
    "Execute(git diff*)",
    "Execute(git log*)",
    "Execute(python *)",
    "Execute(node *)"
  ]
}

Claude can write anywhere and run npm, git (no push), Python, and Node. It can't push, install global packages, or run commands outside that list.

Pattern 2: A team with CI/CD

For teams where CI/CD validates everything:

{
  "allowedTools": [
    "Read", "Grep", "Glob",
    "Write(src/**)",
    "Write(tests/**)",
    "Execute(npm test*)",
    "Execute(npm run lint*)"
  ]
}

More restrictive: Claude can write code and tests, run tests and lint. Everything else requires approval. CI/CD handles full validation on push.

Pattern 3: A sensitive project (fintech, healthcare)

For projects with sensitive data or compliance requirements:

{
  "allowedTools": [
    "Read",
    "Grep",
    "Glob"
  ]
}

Automatic reads only. Everything else requires explicit approval. Every Claude action ends up documented by the manual approval.

Pattern 4: Headless CI/CD

For automated pipelines where Claude runs unsupervised:

# In an ephemeral Docker container
docker run --rm \
  -v $(pwd):/workspace \
  -e ANTHROPIC_API_KEY=$API_KEY \
  claude-image \
  claude --dangerously-skip-permissions \
  -p "Run tests, fix any failures, and create a summary report"

The container is ephemeral (it's destroyed afterward), so the --dangerously-skip-permissions flag is acceptable. The maximum damage Claude can do is bounded by the container.


Pitfalls and edge cases

Pitfall 1: Granting broad Execute permissions

The mistake:

{
  "allowedTools": ["Execute"]
}

The problem: Execute without a pattern lets Claude run any command: rm, curl, sudo, chmod, and so on.

The fix: Always use patterns with Execute:

{
  "allowedTools": [
    "Execute(npm *)",
    "Execute(git status)",
    "Execute(python -m pytest*)"
  ]
}

Pitfall 2: Confusing session permissions with settings

The mistake: Believing that saying always at a permission prompt configures settings permanently.

The reality: always in the interactive prompt sets the permission for the session. For permanent configuration, edit settings.json.

Pitfall 3: Settings.local.json in git

The mistake: Forgetting to add .claude/settings.local.json to .gitignore.

The problem: Your personal permissions (possibly more permissive) get shared with the team.

The fix:

# .gitignore
.claude/settings.local.json

Pitfall 4: --dangerously-skip-permissions as a "shortcut"

The mistake: Using the flag because "I'm tired of confirming every action."

The fix: Configure allowedTools with the actions you want pre-approved. It's just as fast and much safer.

Pitfall 5: Not reviewing the team's permissions

The mistake: Each developer configures their permissions independently, with no team baseline.

The fix: Define settings.json as the team's baseline:

{
  "allowedTools": ["Read", "Grep", "Glob"]
}

Each developer can extend it in settings.local.json, but the project's baseline stays restrictive.


Complete worked example

Scenario: you're setting up a permission policy for a team of 5 developers working on a full-stack project.

settings.json (shared with the team)

{
  "allowedTools": [
    "Read",
    "Grep",
    "Glob",
    "Write(src/**)",
    "Write(tests/**)",
    "Write(docs/**)",
    "Execute(npm test*)",
    "Execute(npm run lint*)",
    "Execute(npm run format*)",
    "Execute(git status)",
    "Execute(git diff*)",
    "Execute(git log*)"
  ],
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write",
        "command": "./scripts/hooks/security-check.sh"
      },
      {
        "matcher": "Execute",
        "command": "./scripts/hooks/safety-gate.sh"
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write",
        "command": "./scripts/hooks/format.sh"
      },
      {
        "matcher": "Write",
        "command": "./scripts/hooks/lint.sh"
      }
    ]
  }
}

settings.local.json (senior developer, personal)

{
  "allowedTools": [
    "Read",
    "Grep",
    "Glob",
    "Write",
    "Execute(npm *)",
    "Execute(git *)",
    "Execute(docker *)",
    "Execute(python *)"
  ]
}

settings.local.json (junior developer, personal)

{
  "allowedTools": [
    "Read",
    "Grep",
    "Glob",
    "Write(src/**)",
    "Write(tests/**)",
    "Execute(npm test*)",
    "Execute(npm run *)"
  ]
}

CLAUDE.md (permissions section)

## Permission policy

### Project baseline (settings.json)
- Claude can read and search files freely
- Claude can write in src/, tests/, and docs/
- Claude can run npm test, npm run lint, npm run format
- Claude can check git state (status, diff, log)
- Everything else requires approval

### For developers
- Set your personal level in .claude/settings.local.json
- Do NOT use --dangerously-skip-permissions
- If you need a permission that isn't in the baseline, ask the team

### Security hooks
- PreToolUse: security check (blocks secrets) + safety gate (blocks dangerous commands)
- PostToolUse: automatic format + lint

The flow in action

Junior developer:
  > "Add validation to the login endpoint"
  
  Claude wants to write src/routers/auth.py
    → allowedTools includes Write(src/**) → ✅ Proceeds without asking
    → security-check.sh hook → ✅ No secrets
    → Claude writes the file
    → format.sh hook → Prettier formats it
    → lint.sh hook → ESLint validates → 0 errors
  
  Claude wants to run npm test
    → allowedTools includes Execute(npm test*) → ✅ Proceeds without asking
    
  Claude wants to run git commit
    → Not in allowedTools → ⚠️ Asks for permission
    
  Developer: "y" (yes, this once)
  Claude makes the commit.

Practice exercises

Exercise 1: Basic — Configure conservative permissions

Configure a conservative permission policy for a new project.

Requirements:

  • Claude can read and search freely
  • Claude can write only in src/ and tests/
  • Claude can run only npm test and npm run lint
  • Everything else requires approval
Solution

Create .claude/settings.json:

{
  "allowedTools": [
    "Read",
    "Grep",
    "Glob",
    "Write(src/**)",
    "Write(tests/**)",
    "Execute(npm test*)",
    "Execute(npm run lint*)"
  ]
}

Verify: ask Claude to write a file at the project root (it should ask for permission) and then in src/ (it shouldn't ask).

Exercise 2: Basic — Personal permissions

Configure your personal permissions to extend the project's baseline.

Requirements:

  • Extend the baseline with permission to run git (status, diff, add, commit)
  • Add permission to run npm scripts
  • Keep the baseline's write restrictions
  • Add settings.local.json to .gitignore
Solution

Create .claude/settings.local.json:

{
  "allowedTools": [
    "Read",
    "Grep",
    "Glob",
    "Write(src/**)",
    "Write(tests/**)",
    "Execute(npm *)",
    "Execute(git status)",
    "Execute(git diff*)",
    "Execute(git add *)",
    "Execute(git commit *)",
    "Execute(git log*)"
  ]
}

Add to .gitignore:

.claude/settings.local.json

Exercise 3: Intermediate — A team policy

Design a permission policy for a team of 3: 1 tech lead, 1 senior, 1 junior.

Requirements:

  • A restrictive project baseline (in settings.json)
  • Each role has a different settings.local.json
  • The junior can't run git push or install packages
  • The senior can git commit but not push
  • The tech lead can push to branches (not to main)
  • Document the policy in CLAUDE.md
Solution

settings.json (baseline):

{
  "allowedTools": [
    "Read", "Grep", "Glob",
    "Write(src/**)", "Write(tests/**)",
    "Execute(npm test*)", "Execute(npm run lint*)"
  ]
}

Junior settings.local.json:

{
  "allowedTools": [
    "Read", "Grep", "Glob",
    "Write(src/**)", "Write(tests/**)",
    "Execute(npm test*)", "Execute(npm run *)",
    "Execute(git status)", "Execute(git diff*)"
  ]
}

Senior settings.local.json:

{
  "allowedTools": [
    "Read", "Grep", "Glob",
    "Write(src/**)", "Write(tests/**)", "Write(docs/**)",
    "Execute(npm *)",
    "Execute(git status)", "Execute(git diff*)",
    "Execute(git add *)", "Execute(git commit *)",
    "Execute(git log*)"
  ]
}

Tech lead settings.local.json:

{
  "allowedTools": [
    "Read", "Grep", "Glob",
    "Write",
    "Execute(npm *)",
    "Execute(git *)"
  ]
}

Exercise 4: Intermediate — Permissions + Hooks combined

Configure a system where permissions and hooks reinforce each other:

  • Permissions allow writes in src/
  • A PreToolUse hook blocks the write if it contains secrets
  • A PostToolUse hook runs lint after writing
  • A permission allows npm test without asking
Solution
{
  "allowedTools": [
    "Read", "Grep", "Glob",
    "Write(src/**)",
    "Execute(npm test*)"
  ],
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write",
        "command": "./scripts/hooks/security-check.sh"
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write",
        "command": "./scripts/hooks/lint.sh"
      }
    ]
  }
}

The permissions open the door (Write in src/). The hooks validate what goes through that door (no secrets, clean lint). That's defense in depth.

Exercise 5: Advanced — A permission audit

Create a script that shows your project's active permissions (Claude Code has no login hook, but you can run this script by hand or wire it into a PostToolUse hook as an audit).

Requirements:

  • Reads .claude/settings.json and .claude/settings.local.json
  • Shows which tools are pre-approved
  • Shows whether hooks are configured
  • Shows a warning if the permissions are too broad
Solution

Create scripts/hooks/audit-permissions.sh:

#!/bin/bash

echo "=== Active permissions ==="

# Read the project settings
if [ -f ".claude/settings.json" ]; then
    echo "📋 Project (settings.json):"
    grep -o '"[A-Za-z]*\([^"]*\)\?"' .claude/settings.json 2>/dev/null | while read -r tool; do
        echo "  ✅ $tool"
    done
fi

# Read the personal settings
if [ -f ".claude/settings.local.json" ]; then
    echo ""
    echo "👤 Personal (settings.local.json):"
    grep -o '"[A-Za-z]*\([^"]*\)\?"' .claude/settings.local.json 2>/dev/null | while read -r tool; do
        echo "  ✅ $tool"
    done
fi

# Warn if Execute without a pattern is allowed
if grep -q '"Execute"' .claude/settings.json .claude/settings.local.json 2>/dev/null; then
    echo ""
    echo "⚠️  WARNING: Execute without a pattern is allowed (every command)"
fi

# Check for hooks
if grep -q '"hooks"' .claude/settings.json 2>/dev/null; then
    echo ""
    echo "🪝 Hooks configured: ✅"
else
    echo ""
    echo "🪝 Hooks: ❌ Not configured"
fi

echo "========================"
chmod +x scripts/hooks/audit-permissions.sh

Note: Claude Code exposes the SessionStart event for exactly this case — you can configure it in .claude/settings.json to run the script automatically at the start of every session. Alternatively, run it by hand (./scripts/hooks/audit-permissions.sh) or reference it from a skill you invoke with /audit-permissions.

Exercise 6: Challenge — A complete governance system

Configure a complete system combining Skills, Hooks, and Permissions for a project:

  1. 3 skills (create-component, write-test, review-code)
  2. Hooks across the 5 real events (PreToolUse, PostToolUse, Notification, Stop, SubagentStop)
  3. Permissions (settings.json baseline + settings.local.json personal)
  4. Documentation in CLAUDE.md

Deliverable: The full .claude/ directory with skills, settings, hook scripts, and the permissions section in CLAUDE.md.

Guide

The final structure:

.claude/
├── skills/
│   ├── create-component.md
│   ├── write-test.md
│   └── review-code.md
├── settings.json
└── settings.local.json

scripts/hooks/
├── session-start.sh
├── security-check.sh
├── safety-gate.sh
├── format.sh
├── lint.sh
└── task-summary.sh

Use the examples from capsules 02, 03, 04, and 05 of this module as your reference. Adapt everything to your project's conventions.

Verify that:

  1. The skills get invoked correctly with /name
  2. The hooks run at the right moments
  3. The permissions block what they're supposed to block
  4. The CLAUDE.md documentation is clear to a new developer on the team

Summary

What you learned in this capsule:

  • Claude Code asks before acting: by default, every action that modifies your system requires approval
  • 3 permission levels: ask every time (default), session permission (temporary), always allow (settings)
  • allowedTools in settings.json configures permanent permissions with granular patterns
  • --dangerously-skip-permissions disables the entire system. Only for automation in isolated environments (containers, CI/CD)
  • The principle of least privilege: start restrictive, open up as you need to
  • Team vs personal permissions: settings.json (team) + settings.local.json (personal)
  • Defense in depth: permissions (what it can do) + hooks (what gets validated) + CI/CD (what gets checked on push)

Summary of the whole module

You've finished Module 5: Skills and Hooks. Here's what you now know how to do:

CapsuleWhat you learned
01 — IntroductionMental model: Skills = commands, Hooks = automation, Permissions = control
02 — SkillsCreating slash commands in .claude/skills/ for repetitive tasks
03 — HooksConfiguring automatic scripts across the 5 lifecycle events
04 — ValidationsBuilding quality pipelines with lint, format, tests, and security checks
05 — PermissionsControlling what Claude can do with granular permission levels

Your Claude Code isn't generic anymore. It has custom skills, validation hooks, and permissions configured for your project. It's a tool built to measure.

Next module: 06 - Subagents — delegating work to specialized agents. If Skills and Hooks are how Claude Code works for you, subagents are how Claude Code delegates work to other agents.


Additional resources

Official documentation

  • Settings — Complete configuration of permissions and scopes
  • Hooks — Hooks as a complement to permissions
  • Interactive Mode — Interactive permissions and shortcuts
  • Headless Mode — Using --dangerously-skip-permissions in automation

Security

Complementary