# Hooks

> Run a command at Raw session, turn, and tool events, with a JSON protocol that can allow or deny a prompt or tool call.

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

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.                                    |

```json
{
  "agents": {
    "coder": {
      "model": "local",
      "tools": { "use": ["builtin/read_file", "builtin/bash"] },
      "hooks": { "use": ["agent/guard"] }
    }
  }
}
```

## Hook folder

Each hook is a folder with a `hook.json` manifest and the script it runs.

```text
hooks/
  guard/
    hook.json
    index.mjs
```

### 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

| 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

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 `UserPromptSubmit` and `PreToolUse`, the object is `{ "decision": "continue" }` or `{ "decision": "deny", "reason": "..." }`. A `message` field, 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 (`UserPromptSubmit` or `PreToolUse`), malformed output, a nonzero exit other than `2`, 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.

> **Caution:**
>
> A hook runs with your full OS account permissions and no sandbox. Raw cannot undo what a script did. If a hook might run again after an interrupted turn, make its side effects idempotent.

## Example: a guard hook

This is the [guard example](https://github.com/lploc94/raw-cli/tree/main/examples/hooks/guard). It denies Bash commands that run `rm`, and it reports Bash failures.

```json
{
  "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
}
```

```js
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

This hook only reports. It cannot deny the `Stop` event.

```json
{
  "protocol_version": 2,
  "name": "notify",
  "events": [{ "name": "Stop" }],
  "command": "python3",
  "args": ["./notify.py"],
  "timeout_ms": 3000
}
```

```python
import json
import sys


event = json.load(sys.stdin)
print(json.dumps({"message": f"Turn {event.get('turn_id', 'unknown')} finished"}))
```

## Install and test a hook

1. Copy the guard folder to `hooks/guard/` beside your config file.

2. Add `"hooks": { "use": ["agent/guard"] }` to the agent you want to guard.

3. Check the configuration. This validates the file without running the script.

   ```sh
   raw --config /path/to/raw.json config list
   ```

4. Ask the agent to run a harmless command, such as `printf`. No guard receipt should appear.

5. Ask the agent to run a command that includes `rm`. The call should be denied, and a `PreToolUse` receipt should appear.

## 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

- [Tools](https://raw.tlelabs.com/docs/extend/tools/) covers the tool calls that hooks can gate.
- [Configuration reference](https://raw.tlelabs.com/docs/reference/configuration/) lists the agent fields.
- [Packages](https://raw.tlelabs.com/docs/extend/packages/) covers sharing a hook as part of a package.
