1
0
Fork 0
claude-code-hooks/exercises/01-block-danger.md
2026-03-30 10:38:57 +02:00

3.8 KiB

Exercise 01: Block Dangerous Commands

Concept: Hooks (CC-024) Level: Basic Time: ~10 minutes


Objective

Set up a pre-tool-use hook that blocks dangerous shell commands before Claude executes them. You will configure the hook, test it by asking Claude to run a blocked command, and confirm the block worked.


Before You Start

Confirm you have:

  • Claude Code installed (claude --version prints a version)
  • This repo cloned and open in Claude Code
  • Python 3 available (python3 --version)

Instructions

Step 1: Read the hook script.

Open hooks/pre-tool-use.sh and read through it. Pay attention to:

  • How it reads input from stdin using cat
  • How it extracts tool_name and command from the JSON
  • What the BLOCKED array contains
  • What exit code 2 means vs exit code 0

You do not need to modify the script. Understanding it is the goal of this step.

Step 2: Make the script executable.

chmod +x hooks/pre-tool-use.sh

This is required before Claude Code can run it as a hook.

Step 3: Configure the hook in .claude/settings.json.

Create the file .claude/settings.json in this repo with this content:

{
  "permissions": {
    "allow": [
      "Bash(ls:*)",
      "Bash(cat:*)",
      "Bash(echo:*)",
      "Bash(pwd)",
      "Bash(date)",
      "Read",
      "Write",
      "Edit",
      "Glob",
      "Grep"
    ],
    "deny": [
      "Bash(rm -rf *)",
      "Bash(sudo *)"
    ]
  },
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "bash hooks/pre-tool-use.sh"
          }
        ]
      }
    ]
  }
}

The matcher field is a regex pattern matched against the tool name. "Bash" matches the Bash tool. ".*" would match all tools.

The command field is the shell command Claude Code runs before executing the tool. It must exit 0 to allow, 2 to block.

Step 4: Start Claude Code in this directory.

cd claude-code-hooks
claude

Claude Code reads .claude/settings.json on startup and registers the hooks.

Step 5: Test the block by asking Claude to run a dangerous command.

Paste this prompt into Claude Code:

Run this shell command: sudo ls /root

Watch what happens. Claude will attempt the Bash tool call. The pre-tool-use hook will run first, match the sudo pattern, and exit 2.

Step 6: Verify the result.

After the hook blocks the command, paste this prompt:

Did the command succeed? Why or why not?

Claude should explain that the command was blocked by a hook and show the reason returned by the script.


Expected Output

After completing the steps, you should see:

  • Claude attempting the sudo ls /root command
  • An error message from the hook: Commands with sudo are not allowed
  • Claude reporting that the command was blocked and could not proceed
  • No output from sudo ls /root because it never ran

If Claude executes the command anyway, check that pre-tool-use.sh is executable and that .claude/settings.json has the correct hook config.


What You Learned

  • PreToolUse hooks intercept before execution. The command never runs if the hook exits 2. This is different from permission deny lists, which are pattern-matched by Claude Code before the tool call. Hooks let you run arbitrary logic.
  • Exit code 2 means block. Exit 0 means allow. Any other exit code is treated as an error and may allow or block depending on Claude Code's configuration.
  • The reason field reaches Claude. Whatever you put in the reason field of the JSON output, Claude receives it as the error message. This lets you explain why a command was blocked in human-readable terms.

Next

Move to Exercise 02: Build an Audit Trail.