# Exercise 02: Build an Audit Trail **Concept:** Hooks (CC-024) **Level:** Intermediate **Time:** ~15 minutes --- ## Objective Add a post-tool-use hook that logs every tool call Claude makes to a local file. You will give Claude a multi-step task, then read the audit log to see everything it did. This is the foundation for compliance logging, debugging, and understanding what Claude actually does during a session. --- ## Before You Start Confirm you have completed Exercise 01, or at minimum: - [ ] `hooks/post-tool-use.sh` exists in this repo - [ ] `.claude/settings.json` exists with the PreToolUse hook from Exercise 01 --- ## Instructions **Step 1:** Read the post-tool-use hook. Open `hooks/post-tool-use.sh` and read through it. Notice: - It always exits 0. Post-tool-use hooks cannot block. - It extracts different keys depending on the tool (`command`, `file_path`, `pattern`) - It appends to `hooks/audit.log` in the same directory as the script itself - The timestamp format is UTC ISO 8601 **Step 2:** Make the script executable. ```bash chmod +x hooks/post-tool-use.sh ``` **Step 3:** Add the PostToolUse hook to `.claude/settings.json`. Update your `.claude/settings.json` to include both hooks: ```json { "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" } ] } ], "PostToolUse": [ { "matcher": ".*", "hooks": [ { "type": "command", "command": "bash hooks/post-tool-use.sh" } ] } ] } } ``` The `matcher: ".*"` pattern matches every tool, not just Bash. This means every Read, Write, Grep, and WebSearch call will be logged. **Step 4:** Start or restart Claude Code. ```bash cd claude-code-hooks claude ``` If Claude Code is already running, restart it so it picks up the new settings file. **Step 5:** Give Claude a multi-step task. Paste this prompt into Claude Code: ``` List the files in this directory, then read README.md, then tell me how many lines it has. ``` This will trigger multiple tool calls: a Bash call for `ls`, a Read call for README.md, and a Bash call for `wc -l`. **Step 6:** Read the audit log. After Claude finishes, paste this prompt: ``` Show me the contents of hooks/audit.log ``` Or read it directly from your terminal: ```bash cat hooks/audit.log ``` --- ## Expected Output The audit log should contain entries like: ``` 2026-03-30T04:12:33Z | Bash | ls -la 2026-03-30T04:12:34Z | Read | README.md 2026-03-30T04:12:35Z | Bash | wc -l README.md ``` Each line shows the UTC timestamp, the tool name, and the primary argument (command, file path, or search pattern). If `audit.log` does not exist after the task, check that `post-tool-use.sh` is executable and that the `PostToolUse` hook is in `settings.json`. --- ## What You Learned - **PostToolUse hooks run after every tool call.** They receive the same JSON input as PreToolUse hooks but cannot influence the outcome. Their job is observation, not control. - **`matcher: ".*"` captures everything.** Narrowing the matcher to `"Bash"` would log only shell commands. Using `".*"` logs all tool types. - **Audit logs survive sessions.** The log file persists between Claude Code sessions. This makes it useful for compliance and debugging: you can always reconstruct what happened. - **Logs are per-project.** The log path is relative to the script, so each project has its own audit trail. --- ## Next Move to [Exercise 03: Prompt-Based Approval](./03-prompt-hooks.md).