Hooks
Most of the time a workflow node is just a prompt and an output. Sometimes you need to tweak what happens around the node: gate which tools the model can call, inject extra context just before the prompt fires, modify the response before the next node sees it. Hooks are the slot for that work.
This page is Advanced. You can write very useful workflows without ever touching hooks.
What a hook is
A hook is a small piece of code that runs at a specific moment around a node's execution. The two most common moments:
- before-tool-use. Runs right before the model is about to call a tool. You can allow it, block it, or modify the tool call.
- post-tool-use. Runs right after a tool finished. You can inspect the result, modify it, or trigger something on the side.
The hook itself is either a YAML block of conditions or a shell command Z.E.N. runs with the tool call as JSON on stdin.
A minimal example
Gate a node so it can only read files (no writes, no shell):
nodes:
- id: review-doc
type: prompt
hooks:
- on: before-tool-use
allow: [Read]
deny: [Write, Edit, Bash]
prompt: |
Review the document at $DOCS_DIR/spec.md and return your notes.Z.E.N. sees the model try to call Bash, the hook says deny, the call is blocked. The model continues without that capability.
Inject context just-in-time
Sometimes the right context depends on the moment, not the workflow:
- id: respond-to-mention
type: prompt
hooks:
- on: before-tool-use
when: tool == "WriteSlackMessage"
inject_context: |
Current time: $NOW_ISO
Current channel topic: $channel_topic.output
prompt: |
Draft a reply to the latest message in $CHANNEL.The injected text is appended to the model's working context at the moment the tool is about to fire.
Run a script as the hook
For more complex logic, point the hook at a script:
- id: classify-email
type: prompt
hooks:
- on: post-tool-use
when: tool == "ReadEmail"
run: scripts/scrub-pii.shThe script gets the tool result on stdin as JSON. Whatever it writes to stdout becomes the new tool result.
Hook scope
Hooks declared on a single node apply only to that node. Hooks declared at the workflow level apply to every node:
name: review-pipeline
hooks:
- on: before-tool-use
deny: [Bash]
nodes:
- id: a
type: prompt
# inherits the deny-bash hook
- id: b
type: prompt
hooks:
- on: before-tool-use
deny: [] # node-level overrides workflow-levelWhy hooks exist (the why-not-just-the-prompt question)
You can ask the model not to use a tool. You can write it in the prompt. The model will usually obey, but "usually" isn't a safety property. A hook is enforcement, not request. If a hook says deny, the tool call is blocked at the runtime, regardless of what the model decides to try.
Use hooks when you need a guarantee. Use prompt instructions for everything else.
Where to learn the schema
The full hook event schema (every event type, every field, every available filter) is documented in the Claude Agent SDK reference. Z.E.N. forwards hook events through to whichever provider the node is using; the schema is provider-defined.