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 --versionprints 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_nameandcommandfrom the JSON - What the
BLOCKEDarray 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 /rootcommand - 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 /rootbecause 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
reasonfield 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.