Hooks, permissions, settings
Customizing agents · topic customizing-agents/hooks-permissions
The mechanical controls around a coding agent: permission modes that decide what it may do unasked, allowlists that widen or narrow that per command, hooks that run your own scripts at fixed points, subagents that isolate work, and the layering of settings between user, project and session.
Concepts
- Permission modes
- Preset levels of how much an agent may do without asking, from approving every edit and command, through auto-accepting edits but asking for commands, to running everything unattended. The mode sets the default; allowlists refine it. Choose per task by what a mistake would cost, and step up only inside a sandbox or a disposable worktree. glossary
- Allowlists
- Explicit lists of tools, commands or paths the agent may use without a prompt, and denylists it may never use. They turn a stream of approval prompts into a one-off decision and make that decision reviewable in configuration. Keep them narrow and specific; a broad wildcard grants much more than the command that prompted it. glossary
- Hooks
- User-defined scripts the agent runs at lifecycle events: before or after a tool call, when a session starts, when the agent finishes. A hook can block an action, transform it, or run a check such as formatting or a secret scan. Hooks enforce rules deterministically where instructions only ask, and their output goes back to the agent so it can react. glossary
- Subagents
- Separate agent instances the main agent delegates to, each with its own context window, tool set and often its own instructions. They keep bulky work such as searching or reviewing out of the main context, allow parallel work and let a task run with narrower permissions than the parent. The parent sees only the subagent's report. glossary
- Settings layering
- Configuration comes from several places that override each other in a fixed order: the user's own defaults, the project's shared settings committed to the repository, local project overrides that are not committed, and flags for one session. Knowing the order explains why a setting seems ignored and lets a team ship safe defaults while people keep personal preferences. glossary
Links
- Builds on: Project instructions, Agent risk
- Leads to: nothing yet
- Competencies drawing on it: Configures an agent for a project, Connects an agent to tools and data safely
Lessons
- Allowing the commands the task needs (tutorial)
- Writing a hook that blocks a mistake (tutorial)
- Choosing a permission mode per task (tutorial)
- Where a setting comes from (explanation)
- Delegating to a subagent with narrower permissions (tutorial)
Your reference
Each lesson above adds its takeaways and its example here once you finish it. Your reference lists every lesson you have finished.
Allowing the commands the task needs
Unlocks when you finish Allowing the commands the task needs.
Takeaways
- An allowlist turns a stream of prompts into a file of rules, one per command or path the task needs, and the file is what makes the decision reviewable. In Claude Code the lists are
allow,askanddenyin a settings file, and/permissionsshows every rule with the file it came from. - Claude Code checks
deny, thenask, thenallow, and the first match decides. A narrower allow rule doesn't win against a broader deny rule. - A rule with no
*matches one exact command. A trailing*after a space also matches the bare command, and a*right after the program name approves every subcommand, including the ones you never thought of. - Write the rule from the tally: sort the prompts into reads, writes and runs, and give the writes and runs one line each. A read inside the working directory never prompts, so it doesn't need a rule.
- After the run, delete every rule that answered no prompt. A rule for a command on the built-in read-only list does nothing, and the don't-ask-again option adds rules to the same file, so prune it after each task.
Example
How wide is each rule? · open in the lesson
What does this print, one line per command, with the number of rules that cover it?
for command in COMMANDS: hits = [r for r in RULES if matches(r, command)] print(f"{command}: {len(hits)}")Prints the lines below (verified in CI from site/examples/customizing-agents/allowlists/width.py)
git log: 3 git log --oneline main: 2 git push origin main: 1
Writing a hook that blocks a mistake
Unlocks when you finish Writing a hook that blocks a mistake.
Takeaways
- A hook is a command of yours that Claude Code runs at a fixed event, such as
PreToolUsebefore a tool call orPostToolUseafter one. A command hook gets the event as JSON on standard input. - For a hook that writes plain text, the exit code is the answer. Code 0 lets the call go on, code 2 blocks it and gives Claude the text on standard error as the reason, and any other code, 1 included, is an error that lets the call run. A JSON decision on standard output can also block.
- A
PreToolUsehook runs before the permission check, in every permission mode. It can stop an action that an allow rule or a mode would have let through. - Keep the hook fast and specific: filter with
if, return early for everything else, and check the rule again in the script, because the filter is best effort. - The message is all the agent learns about your rule. Name what is wrong, and say what to do next, in the words you would use for a new colleague.
Example
The hook from a subdirectory · open in the lesson
From the root of the course repository, run python3 site/examples/customizing-agents/first-hook/subdir.py and compare what you see with the output below.
Prints the lines below (verified in CI from site/examples/customizing-agents/first-hook/subdir.py)
manifest changed, new untracked lockfile: exit 0 lockfile changed alone: exit 2 Blocked: app/package-lock.json changed and app/package.json did not.
Choosing a permission mode per task
Unlocks when you finish Choosing a permission mode per task.
Takeaways
- A permission mode is a preset for what the agent may do without asking. In Claude Code,
defaultprompts for every edit and command,acceptEditsapproves edits inside the working directory and still prompts for other commands,planblocks edits, andbypassPermissionsskips the permission prompts apart from your ownaskrules and the tools that need your answer. - The mode sets the baseline, and allowlists refine it per command. Deny rules hold in every mode, and allow rules do nothing in
bypassPermissions. - Choose the mode by what a mistake would cost in the place the agent works, and say the blast radius out loud first.
- Step a mode up only inside a sandbox or a container, and only as far as the task in front of you needs. A worktree or a branch isn't isolation.
- In a mode that skips prompts, the diff is your review. Read it as carefully as you would have read the prompts.
Example
Count the prompts · open in the lesson
What does this print, one line per mode?
for mode in ("default", "acceptEdits", "bypassPermissions"): count = len(prompts(mode, TASK)) noun = "prompt" if count == 1 else "prompts" print(f"{mode}: {count} {noun}")Prints the lines below (verified in CI from site/examples/customizing-agents/permission-modes/modes.py)
default: 3 prompts acceptEdits: 1 prompt bypassPermissions: 0 prompts
Where a setting comes from
Unlocks when you finish Where a setting comes from.
Takeaways
- Claude Code's settings come from five levels. Highest first: the organization's managed settings, the command line for one session, the uncommitted local project file, the committed project file and the user file. A key with one value takes the highest layer that sets it.
- The permission rule lists merge across files. Every file adds entries, none removes another's, and the merged lists are read deny, then ask, then allow. The layer that decided a call is the one that holds the first matching rule in that order, so a deny in any file blocks what an allow in any other file would approve.
--permission-modebeatsdefaultModefrom every file for one session,--allowedToolsadds allow rules and--disallowedToolsadds deny rules for the session, and none of them overrides a deny in a settings file.- The committed file holds what the whole team needs and gets reviewed with the code. The user file is for your preferences in every project. The local file keeps your exceptions for one project, receives the "don't ask again" approvals, and stays out of git.
- When a setting seems ignored, find the same key in a higher layer. When a command runs unprompted, open
/permissionsand read which file the matching rule came from.
Example
Derive the effective set · open in the lesson
What does this print, one line per tool call?
ACTIONS = [ "Bash(git push origin main)", "Bash(python3 todo.py add milk)", "Bash(rm -rf build)", "Edit(todos.json)", "Bash(git commit -m fix)",]
for action in ACTIONS: print(f"{action}: {decide(LAYERS, action)}")Prints the lines below (verified in CI from site/examples/customizing-agents/settings-layering/effective.py)
Bash(git push origin main): deny (project deny Bash(git push *)) Bash(python3 todo.py add milk): allow (local allow Bash(python3 *)) Bash(rm -rf build): deny (flag deny Bash(rm *)) Edit(todos.json): deny (project deny Edit(todos.json)) Bash(git commit -m fix): prompt (no rule; mode default from project)
Delegating to a subagent with narrower permissions
Unlocks when you finish Delegating to a subagent with narrower permissions.
Takeaways
- A subagent is a separate agent instance with its own context window, system prompt, tool list and permission mode. It starts with that prompt, the task message, the
CLAUDE.mdfiles and agit statussnapshot, and without the main conversation. Its result comes back, and what it read doesn't. - In Claude Code a subagent is a Markdown file in
.claude/agents/or~/.claude/agents/, withnameanddescriptionrequired and the body as its system prompt.toolsnames what it may use,disallowedToolsremoves from what it inherits, and withouttoolsit inherits every tool. - The body asks and the
toolsline enforces. A reviewer withtools: Read, Grep, Globhas no tool for editing or running, whatever its report says it did. /contextbefore and after a delegation shows the saving: the diff and the files it touched were read in the reviewer's window, and the main window took in the report.- A subagent's
permissionModeapplies when the main conversation is indefault,dontAskorplan. InacceptEdits,autoorbypassPermissionsthe subagent runs in the main conversation's mode, and a subagent that declaresbypassPermissionsgets the main conversation's mode instead.
Example
Which tools does each subagent get? · open in the lesson
What does this print, one line per subagent?
for name, fields in DEFINITIONS.items(): print(f"{name}: {', '.join(resolve(POOL, **fields))}")Prints the lines below (verified in CI from site/examples/customizing-agents/subagents/tools.py)
reviewer: Read, Grep, Glob no-writes: Read, Grep, Glob, Bash helper: Read, Grep, Glob, Bash, Edit, Write
Sources
Academy claude-code-101Claude Code 101, Claude Academy (course)Academy claude-code-in-actionClaude Code in action, Claude Academy (course)Academy introduction-to-subagentsIntroduction to subagents, Claude Academy (course)Academy ai-native-sdlc-playbookThe AI-native SDLC playbook, Claude Academy (course)