Hooks
A hook runs a command when Raw reaches a named event, such as before a tool call or when a session starts. A hook can deny a prompt or a tool call. Other events only notify.
Hooks are opt-in. An agent with no hooks.use entry loads no hooks.
Select hooks
Section titled “Select hooks”An agent lists its hooks in hooks.use. Hooks run in the order listed.
| Prefix | Where Raw loads it from |
|---|---|
agent/<name> |
hooks/<name>/ beside the selected config file. |
local/<name> |
~/.config/raw/hooks/<name>/, or the $XDG_CONFIG_HOME equivalent. |
pkg/<alias>/hooks/<export> |
A hook from an installed package. |
{ "agents": { "coder": { "model": "local", "tools": { "use": ["builtin/read_file", "builtin/bash"] }, "hooks": { "use": ["agent/guard"] } } }}Hook folder
Section titled “Hook folder”Each hook is a folder with a hook.json manifest and the script it runs.
Directoryhooks/
Directoryguard/
- hook.json
- index.mjs
Manifest fields
Section titled “Manifest fields”| Field | Required | Rules |
|---|---|---|
name |
Yes | Name of the hook. |
events |
Yes | A nonempty array of subscriptions. See below. |
command |
Yes | An executable on PATH, or a ./ path inside the hook folder. No shell expands it. |
args |
No | Literal arguments. An argument that begins with ./ resolves inside the hook folder. |
timeout_ms |
No | Default 5000. Maximum 30000. |
protocol_version |
Yes | Must be 2. |
Each entry in events has a name. Tool events can also have:
| Field | Meaning |
|---|---|
match |
A glob over the canonical tool ID, such as builtin/bash or mcp/search/*. |
when |
An argument filter with the same form as tools.rules, such as { "source": "arguments", "any": "commands[*].command", "regex": "..." }. |
match and when are valid only on tool events.
Events
Section titled “Events”| Event | When it fires |
|---|---|
SessionStart |
A session attaches to the runtime, including on resume. |
UserPromptSubmit |
A user prompt is submitted. Can deny. |
PreToolUse |
Before a tool call runs. Can deny. |
PostToolUse |
After a tool handler ran successfully. |
PostToolUseFailure |
After a tool handler ran and failed. |
Stop |
A turn finishes. |
SessionEnd |
The runtime attachment closes. Stored history is not deleted. |
Tool hooks see built-in, local, MCP, and ACP tools alike. Calls that policy denies, hides, rejects as invalid, or that the user denies at approval do not fire tool hooks.
Protocol
Section titled “Protocol”Raw sends one UTF-8 JSON object on the hook’s stdin. It contains protocol_version: 2, the event name, the session cwd, optional agent, session, and turn IDs, and an event-specific payload.
The hook responds in one of these ways:
- Print nothing and exit
0. Raw continues. - Print one JSON object. For
UserPromptSubmitandPreToolUse, the object is{ "decision": "continue" }or{ "decision": "deny", "reason": "..." }. Amessagefield, if present, is shown to the user. - Exit with code
2. Raw denies. This wins over any JSON the hook printed.
For other events, the hook can only send a message, which is shown as a notification. It cannot change a result that has already happened.
Failures are handled by event type:
- On a gate event (
UserPromptSubmitorPreToolUse), malformed output, a nonzero exit other than2, a spawn failure, a timeout, or oversized output blocks the action. - On a notification event, the same failures only produce a warning.
A continue response never overrides an ask or deny rule in tools.rules.
Raw does not log hook input or raw stdout by default. Every hook run produces a short receipt in the live output and in saved history, with the hook ID, event, outcome, elapsed time, and any message or error code.
Example: a guard hook
Section titled “Example: a guard hook”This is the guard example. It denies Bash commands that run rm, and it reports Bash failures.
{ "name": "guard", "events": [ { "name": "PreToolUse", "match": "builtin/bash", "when": { "source": "arguments", "any": "commands[*].command", "regex": "(^|[;&|()\\n])\\s*rm(\\s|$)" } }, { "name": "PostToolUseFailure", "match": "builtin/bash" } ], "command": "node", "args": ["./index.mjs"], "timeout_ms": 5000, "protocol_version": 2}let input = "";process.stdin.setEncoding("utf8");process.stdin.on("data", chunk => input += chunk);process.stdin.on("end", () => { const request = JSON.parse(input); if (request.event === "PreToolUse") { process.stdout.write(JSON.stringify({ decision: "deny", reason: "Review removal commands first" })); } else { process.stdout.write(JSON.stringify({ message: "Bash command failed" })); }});The regex is a text match, not a shell parser. Pair a hook like this with a tools.rules entry if you need stronger protection.
A notification hook in Python
Section titled “A notification hook in Python”This hook only reports. It cannot deny the Stop event.
{ "protocol_version": 2, "name": "notify", "events": [{ "name": "Stop" }], "command": "python3", "args": ["./notify.py"], "timeout_ms": 3000}import jsonimport sys
event = json.load(sys.stdin)print(json.dumps({"message": f"Turn {event.get('turn_id', 'unknown')} finished"}))Install and test a hook
Section titled “Install and test a hook”-
Copy the guard folder to
hooks/guard/beside your config file. -
Add
"hooks": { "use": ["agent/guard"] }to the agent you want to guard. -
Check the configuration. This validates the file without running the script.
Terminal window raw --config /path/to/raw.json config list -
Ask the agent to run a harmless command, such as
printf. No guard receipt should appear. -
Ask the agent to run a command that includes
rm. The call should be denied, and aPreToolUsereceipt should appear.
Limits
Section titled “Limits”- A selected hook folder can hold at most 256 regular files and 16 MiB in total. Links are rejected.
- Hook changes apply on the next attachment or turn, including resume. A running runtime keeps the version it loaded.
- The hook’s stdin, stdout, and stderr are size-bounded. Raw ends the process tree when the timeout or the cleanup window expires.
Related
Section titled “Related”- Tools covers the tool calls that hooks can gate.
- Configuration reference lists the agent fields.
- Packages covers sharing a hook as part of a package.