← Blog

Claude Code Hooks: How to Turn Agent Actions Into an Audit Trail

Marco Masut

"Claude code hooks" gets searched by people who already run Claude Code and have noticed something specific: before a tool call happens, or right after it, there's a moment where a script can run. That moment is where most of the tooling built around Claude Code today, permission gates, linting, notifications, gets wired in. It's also, less deliberately, the closest thing the agent has to a place where a record of what it did could get written down.

What Claude Code hooks actually intercept

A hook is a command Claude Code runs at a specific point in its lifecycle: before a tool call executes, after it succeeds or fails, when a session starts or ends, when the agent is about to stop responding, before context gets compacted, and more than a dozen other points documented in Claude Code's own hooks reference. The hook receives a JSON payload on stdin, typically the tool name and its input for a tool-level hook, and it talks back through its exit code and, optionally, structured JSON on stdout: exit 2 hard-blocks the action, a JSON permissionDecision of allow, deny, or block gives finer control, and plain output can get added back into the agent's context. Configuration lives in settings.json, project-level or user-level, as a plain list of matchers and commands, no separate service to run.

That's a genuinely useful mechanism, and it's why hooks became the default answer to "how do I stop Claude Code from doing X" or "how do I get notified when it does Y." A PreToolUse hook can block a command that matches rm -rf. A PostToolUse hook can run a linter on every file the agent just edited. A SessionStart hook can load project-specific context automatically. None of that requires trusting the model to police itself, which is exactly the appeal: the check runs outside the agent's own reasoning, deterministically, every time the matching event fires.

The tutorials that already exist, and what they leave out

Search for hooks tutorials today and almost everything you find is a walkthrough of the mechanism itself: which events exist, how matchers work, how to write a PreToolUse script that blocks a dangerous command, how to pipe PostToolUse output into a log file with jq and a timestamp. That coverage is accurate and useful as far as it goes, and one popular Reddit thread built its reputation specifically on cataloguing every hook event with a working example for each one. What it doesn't do, because it isn't trying to, is ask what that log file is actually good for once it exists.

A shell script that appends one line per tool call to ~/.claude/audit.log produces something that looks like an audit trail. It records a tool name, an input, maybe a timestamp. It's still just a local text file that anyone with filesystem access can edit, delete, or never have written correctly in the first place if the script had a bug nobody caught. Calling that an audit trail is doing the word a favor.

From a hook log to an evidence trail: what is missing

An event log and a piece of evidence answer different questions. A log answers "what happened." Evidence has to also answer "can I trust that this is what happened, and can I verify who authorized it." A hooks-based log, on its own, is unsigned, lives wherever the developer or the CI runner happened to write it, and has no cryptographic link back to the task or the person who requested the work in the first place. Someone with write access to that log file can edit history after the fact, and nothing in the mechanism itself would catch it.

That gap is not specific to hooks. It's the same one most coding agent roundups skip when they judge a tool purely on what it produces inside a session: they don't ask what's left afterward to show someone who wasn't there. Hooks are actually a step closer to an answer than most of the category gets, because they intercept the raw events as they happen rather than reconstructing them after the fact from a chat transcript. A step closer isn't the same as there. Interception is not attestation, and a JSON line in a log file is not a signature.

Where a hand-rolled hook script breaks at team scale

A single developer running a PostToolUse hook that appends to a local file works fine for that one developer, on that one machine, for as long as nobody needs to check the log later. It stops working the moment a second person asks a question the log was never built to answer: which of the twelve sessions that touched this file this week is the one that introduced this regression, who reviewed it, and can I see that review tied to the specific commit. A local text file per developer doesn't survive that question. Neither does a shared log with no access control, no tamper detection, and no schema that a second tool can parse reliably six months later when the person who wrote the original jq script has moved teams.

The same permission question Claude Code's own auto mode raises applies here in a different form: a classifier blocking a dangerous command mid-session and a hook writing a line to a log file solve two different problems, and neither one is "who signed off on what shipped." Auto mode's classifier stops an accident before it happens. A hook log records that something happened. Neither produces the artifact a reviewer six months later actually needs.

What a hook-based log needs before it can count as proof

Hooks are the right place to start, not because they're perfect, but because they're the one mechanism in Claude Code that sees the raw event as it happens, before the agent's own narrative gets layered on top. What turns that raw capture into something closer to proof is everything the mechanism doesn't provide on its own: a record that's append-only or cryptographically chained rather than a plain file anyone can rewrite, a link from every logged action back to the task and the person who authorized it, and a point where a human actually signs off on what's about to ship, not just a script confirming the session happened.

That's the layer above the agent, not inside it: the system prepares the record, hooks included, and a person signs before it counts as delivered. A hook script that logs faithfully is a good building block. On its own, it's still a log a team is trusting, not proof a team can hand someone else.