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

3.9 KiB

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.

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:

{
  "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.

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:

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.