Build with AI

What are hooks and permissions in coding agents?

· By Robert Scales · 4 min read

Short answer

Permissions are rules that decide which tools a coding agent may use freely, which need your approval and which are forbidden. Hooks are your own small scripts that run automatically at set moments, such as just before the agent runs a command, and can inspect the action and block it, which turns a house rule into something the agent cannot skip.

A coding agent is an AI model that works in your project: it reads files, edits code and runs terminal commands to reach a goal you describe. Instructions in a file like CLAUDE.md tell it how you like things done, but instructions are requests. The model usually follows them, and sometimes it forgets or misreads them. When a rule really matters, such as "never commit a password," you want something firmer.

Two features provide it. Permissions control access: what the agent may do without asking, what needs your approval, and what is off limits. Hooks add logic: they are scripts that run automatically at fixed points in the agent's work and can stop an action that breaks your rules. This article uses Claude Code, Anthropic's coding agent, as the example; other agents offer comparable controls under different names.

Permissions: allow, ask, deny

By default, Claude Code is cautious. Anthropic designed it to ask before anything that could change your system, such as writing files or running many shell commands. That is safe but slow, so you can adjust the rules in a settings file. A project's .claude/settings.json can be committed to your repository so the whole team shares it.

Rules go into three lists:

{
  "permissions": {
    "allow": ["Edit", "Bash(npm test:*)", "Bash(git commit:*)"],
    "ask":   ["Bash(git push:*)"],
    "deny":  ["WebFetch", "WebSearch"]
  }
}

Read it as: file edits, running the test suite and making commits happen without interruption; pushing to a shared repository always needs a yes from you; fetching web pages and searching the web are not allowed at all. Anything not listed keeps the default behavior, which for most shell commands is to ask. The form Bash(git commit:*) means "shell commands that start with git commit."

Anthropic's own strict example for organizations goes further: it requires approval for every shell command, denies web tools, and turns off the mode that skips all permission checks. Anthropic warns that skipping checks entirely can lead to lost data or to attacks hidden in content the agent reads, and suggests doing it only inside an isolated container with no internet access.

Hooks: your checks, run every time

Permissions answer "may the agent use this tool?" Hooks answer "is this particular action acceptable?" A hook is attached to an event. The most useful one for safety is PreToolUse, which fires just before the agent uses a tool. A matcher picks which tool the hook watches, such as Bash for shell commands.

When the hook runs, Claude Code sends it a small JSON message on standard input describing the action, including the tool name and its input; for a shell command, the command text is in tool_input.command. The hook answers with its exit code, the number a program returns when it finishes:

Exit codeWhat happens
0The action goes ahead
2The action is blocked, and the hook's error message is shown to the agent so it can change course
Other (such as 1)The message is shown to you, but the action is not blocked

A worked example: block commits that contain secrets

The goal: whenever the agent tries to run git commit, look at the staged changes and stop the commit if they contain something that looks like a key or a password. Save this as .claude/hooks/block_secrets.py:

#!/usr/bin/env python3
import json, re, subprocess, sys

PATTERNS = [
    (r"AKIA[0-9A-Z]{16}", "an AWS access key ID"),
    (r"-----BEGIN [A-Z ]*PRIVATE KEY-----", "a private key"),
    (r"(?i)(api[_-]?key|secret|token|password)\s*[:=]\s*['\"][^'\"]{12,}['\"]",
     "a hard-coded credential"),
]

data = json.load(sys.stdin)
command = data.get("tool_input", {}).get("command", "")
if not re.search(r"\bgit\s+commit\b", command):
    sys.exit(0)  # not a commit: nothing to check

diff = subprocess.run(["git", "diff", "--cached", "-U0"],
                      capture_output=True, text=True,
                      cwd=data.get("cwd") or ".").stdout
added = "\n".join(l for l in diff.splitlines() if l.startswith("+"))

found = [label for pattern, label in PATTERNS if re.search(pattern, added)]
if found:
    print("Commit blocked: staged changes appear to contain "
          + ", ".join(found) + ". Remove the secret, load it from an "
          "environment variable or secrets manager, then commit again.",
          file=sys.stderr)
    sys.exit(2)  # block the tool call and tell the agent why

Then register it in .claude/settings.json, next to your permissions:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command",
            "command": "python3 .claude/hooks/block_secrets.py" }
        ]
      }
    ]
  }
}

If the agent stages a file containing an illustrative line such as API_KEY = "example-not-a-real-key-123" and tries to commit, the hook exits with 2. The commit never runs, and the agent receives the message explaining why, so it can move the value into an environment variable and try again.

Try it: Add the hook to a test project, create a file with a fake key like the one above, and ask the agent to commit it. Then remove the key and ask again; the second commit should go through.

How do you use them to enforce your rules?

Start by sorting your rules by how much damage a slip would cause. Style preferences belong in CLAUDE.md. Rules where a mistake is costly or hard to undo, such as leaking credentials, pushing to the main branch or deleting data, belong in permissions or hooks, because those do not depend on the model remembering.

Use permissions for whole categories of action: deny tools the project never needs, keep risky commands on "ask," and allow only the routine ones you would approve anyway. Use hooks when the decision depends on the details, as in the example, where git commit is fine except when the content is not.

Keep each hook small, fast and focused on one rule, and write its error message for the agent: say what was wrong and what to do instead, so it can fix the problem on its own. Test hooks with a deliberate violation before relying on them.

Finally, know the limits. A hook only sees what the agent does through its tools; a commit you type yourself in another terminal bypasses it, so keep a regular git pre-commit check or a secret scanner in your repository too. Pattern lists also miss secrets they were not written for. For teams, Anthropic's example settings show how administrators can lock permission rules and hooks centrally, so individual projects cannot quietly loosen them.

Sources

  1. Claude Code Hook: Bash Command Validator (examples/hooks/bash_command_validator_example.py) — Anthropic (GitHub, anthropics/claude-code), July 6, 2026
  2. Settings Examples (examples/settings: README.md and settings-strict.json) — Anthropic (GitHub, anthropics/claude-code), July 6, 2026
  3. Claude Code: Best practices for agentic coding — Anthropic, April 18, 2025

Get the free Guide to Artificial Intelligence

Sign up for the weekly newsletter and receive the free guide: what changed in AI this week, one lesson, one prompt to try. Unsubscribe in one click.

How we handle your address

Back to top

Display

Your choices are saved in this browser only. No data is sent.

Text size
100%
Colours
Reading
More settings
Accessibility statement