Claude Code hooks tutorial: guardrails, logging, and rule packs
If you searched for a claude code hooks tutorial, you probably want three concrete things: intercept what the agent does, keep an audit trail, and block risky actions before they happen. Claude Code does not ship with a built-in governance layer for that. agentcheck fills the gap by wrapping each Claude process with hook-based checks, logging tool calls to JSONL, and enforcing rule packs defined in YAML.
The useful mental model is simple: Claude runs as usual, but every tool invocation passes through hooks that can inspect, allow, or reject the action. That makes this setup helpful for local development, CI experiments, and teams that need to answer “what did the agent actually do?” after the fact.
Install and basic usage
Install agentcheck globally from GitHub:
npm install -g github:paprika-org/agentcheck
Once installed, you run Claude Code through agentcheck instead of launching it directly. The exact wrapper command may evolve with the project, but the practical pattern is:
agentcheck -- claude
That means agentcheck becomes the parent process, attaches hooks around the Claude session, and starts recording events. If you want a dedicated rules file, pass it explicitly:
agentcheck --rules ./rules.yaml -- claude
This is the first place where a claude code hooks tutorial should be honest: hooks are not magic. They can constrain tool usage and provide visibility, but they do not replace reviewing prompts, repository permissions, or sandboxing. They are a control layer, not a complete isolation boundary.
What a rule pack looks like
Rule packs are YAML files that express what the agent is allowed to do. A useful starter pack is one that permits read operations broadly but restricts destructive shell commands and writes outside the current repository.
version: 1
rules:
- id: allow-safe-read
description: Permit non-destructive read commands
match:
tool: shell
command_regex: "^(ls|pwd|cat|sed|find|rg)\\b"
action: allow
- id: block-destructive-git
description: Prevent hard resets and forced cleans
match:
tool: shell
command_regex: "^(git reset --hard|git clean -fdx)\\b"
action: deny
- id: block-parent-writes
description: Deny writes outside the repo root
match:
tool: write_file
path_regex: "^\\.\\./"
action: deny
- id: log-everything
description: Always emit an audit event
match:
tool: ".*"
action: log
The exact schema depends on the project version, but the core idea is stable: match a tool call, decide whether to allow or deny it, and write the event to a log. If you have used policy engines before, this will feel familiar, but lighter-weight.
Reading the audit log
One of the most practical outcomes of using hooks is the JSONL log. Each line is an event you can grep, archive, or feed into a later analysis step. For example:
{"ts":"2026-04-23T11:14:07Z","tool":"shell","args":{"command":"rg TODO src"},"rule":"allow-safe-read","decision":"allow"}
{"ts":"2026-04-23T11:14:24Z","tool":"shell","args":{"command":"git reset --hard"},"rule":"block-destructive-git","decision":"deny"}
{"ts":"2026-04-23T11:15:01Z","tool":"write_file","args":{"path":"../secrets.txt"},"rule":"block-parent-writes","decision":"deny"}
This is enough to answer questions that normally become guesswork: which command was attempted, when it happened, and which rule made the decision. For teams running repeated agent tasks, JSONL is also easier to process than ad hoc terminal output.
rg '"decision":"deny"' ./agentcheck.log.jsonl
jq -c 'select(.tool == "shell")' ./agentcheck.log.jsonl
tail -n 20 ./agentcheck.log.jsonl
Where hooks help most
The best use cases are narrow and operational:
- Blocking a known-dangerous command set such as destructive Git or filesystem operations.
- Capturing a durable audit trail of tool activity during long agent sessions.
- Applying different rule packs for local development versus CI.
- Making agent behavior legible enough to debug failures or policy violations.
If your main problem is “I need the agent to never touch production credentials,” hooks are useful because you can deny known access patterns and log every attempt. If your problem is “I want a proof that no harmful behavior is possible,” hooks alone are not enough. You still need environment isolation and permission boundaries outside the agent process.
Practical workflow for a repo
A workable pattern is to start permissive, inspect the log, then tighten rules based on what the agent actually tries to do. In other words, do not design a perfect rule pack in the abstract. Let the session reveal its real command and file access patterns.
# 1. Run with logging first
agentcheck --rules ./rules-observe.yaml -- claude
# 2. Review actual tool usage
jq -r '.tool' ./agentcheck.log.jsonl | sort | uniq -c
# 3. Tighten the policy
cp rules-observe.yaml rules-enforce.yaml
# 4. Re-run with deny rules enabled
agentcheck --rules ./rules-enforce.yaml -- claude
This is usually more productive than starting with a highly restrictive file that blocks basic navigation, search, and read operations. The agent needs enough freedom to inspect the repo, but not enough freedom to cause irreversible damage.
What this tutorial does not solve
No serious claude code hooks tutorial should pretend hooks solve everything. They do not guarantee prompt quality. They do not stop data exfiltration if the allowed tool surface is too broad. They do not automatically understand business context. They are a strong operational layer for behavior governance, especially when paired with repository scoping, network limits, and reviewable logs.
They also introduce maintenance cost. Someone has to own the rule packs, revisit them when workflows change, and decide whether a deny event represents a real attack, an expected false positive, or a missing safe path. That tradeoff is normal. If you want control, you have to encode policy somewhere.
Next step: if you want to try this on a real Claude session, start with the GitHub repo, install the tool, and build a small rules file around the commands your team already allows. The project is here: github.com/paprika-org/agentcheck.