An agent hook is a script or callback that an agent runtime runs automatically at a defined point in an agent’s work. Hooks matter for agents because they let you check, record, or block an action in code, whether or not the model follows its instructions.
For example, suppose a coding agent proposes the shell command rm -rf ~/projects. Before the command runs, a pre-tool hook reads it, matches a blocked pattern, and exits with code 2. In a runtime that treats that exit code as a block, the command never runs.
Hooks typically fire at events such as before a tool call, after a file edit, or when a session stops. They signal their decision through exit codes or structured output that allow, warn, or deny.
Build better agents with Arize
Trace, evaluate, and learn. Build agents that work with Arize AX and start tracing your runs today.
Prefer open source? Try Arize Phoenix for self-hosted, open source agent observability.
Why agent hooks matter
A prompt instruction asks the model to behave. A hook is runtime code that checks behavior after the model decides and before the action happens.
Hooks are useful for:
- Blocking risky actions: Stop a specific command, file path, or network target before it executes.
- Enforcing required steps: Run a formatter, linter, or test after every edit and feed failures back to the agent.
- Checking completion: Confirm before a session ends that required steps, such as a test run or trace export, actually happened.
- Recording telemetry: Log each decision so a blocked or altered action can be explained later.
The model does not choose whether a registered hook fires. That makes hooks closer to deterministic controls than to guidance in the agent’s context.
How agent hooks work
A hook has four parts:
- Event: The lifecycle point that triggers the hook, such as before a tool call.
- Matcher: A filter that narrows which tools or actions trigger it.
- Handler: The executable command or function that runs.
- Result contract: The rules for how the hook reports success, a warning, or a blocking error.
When the event occurs, the runtime serializes context about the pending action. This typically includes the tool name, arguments, working directory, and session identifier. The runtime passes it to the hook, commonly as JSON on standard input.
The hook then returns its decision. The runtime allows the action, shows a warning, or blocks it.
Exact event names, fields, and blocking rules differ by product. The Claude Code hooks documentation is one concrete reference implementation. It is not a cross-vendor standard.
Common hook events
Hook events follow the shape of an agent run:
- Session start: Prepare the environment or load context.
- Pre-tool: Validate a command, file path, or network target before it runs.
- Post-tool: Run formatters, tests, or linters after an edit, and return failures to the agent.
- Stop: Check that required steps happened before the session ends.
These events place hooks in the runtime control path, not inside the prompt. For more on that control path, see agent policy layer.
How to write an agent hook
The following pre-tool hook for a shell tool reads the proposed command from standard input. It blocks recursive deletes that start at an absolute path or the home directory, using a naive pattern match.
The JSON field names are illustrative. Match them to the runtime you use.
#!/usr/bin/env bash
# Pre-tool hook: naive pattern block for rm -rf on absolute or home paths
input=$(cat)
command=$(printf '%s' "$input" | python3 -c 'import json,sys; print(json.load(sys.stdin)["tool_input"]["command"])')
case "$command" in
*"rm -rf /"*|*"rm -rf ~"*) echo "Blocked: rm -rf on an absolute or home path" >&2; exit 2 ;;
esac
exit 0
The script extracts the command, checks it against two patterns, and exits with code 2 on a match. Exit code 0 lets the call proceed.
Test the hook with sample payloads
You can test a hook outside the agent by piping sample payloads into it. This loop sends four commands to the script, saved as hook.sh, and prints each exit code.
for c in 'rm -rf ~/projects' 'rm -rf build/' 'rm -fr /tmp/x' 'rm -rf ../..'; do
echo "{"tool_name":"Bash","tool_input":{"command":"$c"}}" | ./hook.sh
echo "$c -> exit $?"
done
Output:
Blocked: rm -rf on an absolute or home path
rm -rf ~/projects -> exit 2
rm -rf build/ -> exit 0
rm -fr /tmp/x -> exit 0
rm -rf ../.. -> exit 0
The output shows how simple the string match is. It blocks the home-directory delete but misses reordered flags like rm -fr and relative paths like ../... It would also block absolute paths inside the workspace.
What matters is placement. Even if the model produces the command, the runtime evaluates the hook before the tool call executes.
Add a post-edit hook
In the same setup, a post-edit hook could run the test file nearest the edited file and return its output. The agent then sees the failure in its next step instead of declaring success.
How to monitor and debug agent hooks
Treat hooks as production code paths. For each firing, record these fields:
- Event: Which lifecycle point fired.
- Matcher result: Whether the matcher selected this action.
- Decision: Allow, warn, or block.
- Duration: How long the hook took.
- Trace reference: The agent trace and span that triggered it.
From those records, track block rate by rule, hook error and timeout rate, and added latency per tool call. Also track how often a post-tool hook’s failure changed the agent’s next action.
Arize’s write-up on Kiro CLI observability with Arize skills shows tracing for a coding-agent command-line interface (CLI). Hook events belong in that same trace view.
When an agent seems to ignore a rule
First check whether the hook fired at all. Then check whether its matcher covered the tool name the agent actually used.
Most apparent hook failures are matcher or event-name mismatches. For more on tracing problems like this, see debugging agents.
Limitations of agent hooks
Hooks are useful but narrow:
- Limited visibility: Hooks only see the events and fields the runtime exposes.
- Matcher gaps: A matcher written for one tool name silently misses the same action through another tool.
- Brittle string matching: Equivalent commands with different quoting or paths can slip past a pattern.
- Added latency: Every hook adds time to the events it covers.
- New failure surface: A slow or crashing hook can stall every tool call.
Decide explicitly, for each event, on the timeout, whether the hook fails open or fails closed, and how you version the hook scripts.
Agent hooks vs. prompt instructions and sandboxes
A prompt instruction is guidance the model may or may not follow. A hook runs whether or not the model complies.
Hooks are also narrower than the full set of agent security controls. A hook can block a listed command pattern. It does not provide sandboxing, credential isolation, or network policy by itself.
Use hooks for fast, local checks tied to specific events. Place broader authority limits in the execution environment. For more on that layer, see agent sandbox.
Frequently asked questions
Are agent hooks the same across Claude Code, Kiro, and other coding agents?
No. The lifecycle idea is shared, but event names, input schemas, and blocking contracts are product-specific. Write hooks against the documentation of the runtime that will execute them.
Can a hook change what the agent does, or only block it?
It depends on the runtime. A hook may block an action, emit a warning the agent sees, inject extra context, or return output that feeds into the next model step. Check which of those the specific event supports before you design around it.
Should safety rules live in hooks or in the prompt?
Put rules that must hold in runtime controls, including hooks and permissions. Keep prompt text for guidance and explanation. If a rule matters, do not rely on the model choosing to follow it.
How do I test an agent hook?
Feed it recorded hook-input payloads for allowed, blocked, and malformed cases, and assert on the exit code and output. Then enable it in observe-only mode in real sessions before you turn on blocking.
Why didn’t my hook fire?
Usually the event name or matcher does not match what the runtime emitted. Compare the tool name in the agent trace with the matcher in your hook configuration.
Putting agent hooks to work
Start with one pre-tool hook for the action you most need to stop and one post-edit hook that runs tests. Test both against recorded payloads before you enable blocking.
For each hook, write down its event, matcher, timeout, and fail-open or fail-closed behavior. Recheck those against the runtime’s current documentation when you upgrade it.
Log every hook decision next to the agent’s trace. Arize AX can hold those spans in one view, so a blocked or altered action is easy to explain later.