> ## Documentation Index
> Fetch the complete documentation index at: https://docs.surfacearea.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Hooks in Claude Code and coding agents

> Run gateway-hooks.toml as a coding agent's hook — one settings entry, the agent's own event and answer contract, personas that survive across the process the agent spawns per event.

A coding agent runs a hook as a command: it spawns the command per event, writes the event as
JSON on stdin, and reads a verdict on stdout. `gateway hooks run` is that command for a
`gateway-hooks.toml`: it reads the agent's event, runs the rules that match, and answers in the
agent's own contract. The rules file is the same one an SDK agent attaches in code.

## One settings entry

Claude Code, `.claude/settings.json` (project) or `~/.claude/settings.json` (user):

```json theme={null}
{
  "hooks": {
    "PreToolUse":  [{ "matcher": "Bash|WebSearch|WebFetch", "hooks": [{ "type": "command", "command": "gateway hooks run" }] }],
    "PostToolUse": [{ "matcher": "WebSearch|WebFetch",      "hooks": [{ "type": "command", "command": "gateway hooks run" }] }],
    "Stop":        [{ "hooks": [{ "type": "command", "command": "gateway hooks run" }] }]
  }
}
```

The agent's `matcher` decides which tool events reach the command; the file's `matcher` decides
which rules run for the tool the event names. Keep both, or open the agent's side wide and let
the file decide — the file is the one you version with the task.

The command finds its rules at `--config`, else `$GATEWAY_HOOKS_CONFIG`, else
`gateway-hooks.toml` in the agent's working directory. Other coding agents that copied Claude
Code's hook contract (event names `PreToolUse` / `PostToolUse` / `Stop`, `hook_event_name` and
`tool_input` on stdin, `hookSpecificOutput` on stdout) run the same command.

## What comes back

| Event | The rules run | The answer |
| - | - | - |
| `PreToolUse` | `pre_tool_call` | `permissionDecision: "allow"`, with `updatedInput` when a rule rewrote the input (the alias door resolving a persona, a command's `updated_input`) and `additionalContext` when one added context; a deny is `permissionDecision: "deny"` with the reason |
| `PostToolUse` | `post_tool_call` | `additionalContext` when a rule added it; `record` handlers have written their sink |
| `Stop` / `SessionEnd` | `run_end` | nothing on a pass; `decision: "block"` with the reason when a `run_end` handler fails the run — an outgoing scan that found a real value in the transcript's last assistant turn, a command that exited 2 |
| anything else | — | passed through (`{}`), with a line on stderr saying so |

```bash theme={null}
echo '{"hook_event_name":"PreToolUse","session_id":"s1","cwd":".","tool_name":"Bash","tool_input":{"command":"rm -rf /"}}' \
  | gateway hooks run --config gateway-hooks.toml
# {"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"destructive command"}}
```

`--json` adds a `gateway` key with the run's meters (aliases, substitutions, leaks, denials,
hook errors); the agent ignores it, a human reads it.

## What persists between events

The agent spawns a fresh process per event, so the run's state lives on disk: `--state <dir>`,
else `$GATEWAY_HOOKS_STATE`, else `.gateway/hooks/<session id>` under the agent's working
directory (Claude Code passes `session_id` and `cwd` with every event). The directory holds
`run.json` — the run key and the seeds — and it is what `{run_dir}` means in a `record` sink or
a `[live]` path.

The alias door derives every persona from the run key and the seeds alone, so re-seeding on
each event reproduces the same map: the persona the model saw in event 3 resolves to the same
real value in event 40. Seed once (`--seed @entities.json`, kept with the state) or on any later
event (new entities join the run; the key stays). The run's meters are per process; the sinks
`record` handlers write are the durable log.

The state directory holds the run key, which with the legend maps personas back to real
people: keep it out of version control (`.gateway/` is the place for it) and treat it like the
credentials next to it.

## What the coding-agent contract covers

* **Deny, rewrite the input, add context, record** — all of it lands, exactly as in the SDK.
* **The alias door works outbound.** A persona the model typed into a tool call is resolved to
  the real value before the tool runs (`updatedInput`).
* **Inbound rewrites run in the SDK or a proxy.** A coding agent shows the model a tool's
  result before its `PostToolUse` hook runs. For a tool whose results carry private values,
  route the tool through the SDK (`run.wrapTools` / `run.wrap`, or a `[tools.*]` entry the SDK
  materializes) or through a proxy that answers the agent, where the inbound rewrite is applied
  before the model reads anything. In the hook, `gateway hooks run` meters what the rule would
  have rewritten and reports it on stderr (`rewrite reached nobody`) and as
  `inbound_rewrite_unreachable: true` under `gateway`.
* **`run_end` sees the last assistant turn**, read from the agent's transcript when it passes
  `transcript_path`; a `command` handler scans a report the agent wrote to a file.

## Checking before wiring

```bash theme={null}
gateway hooks check gateway-hooks.toml --event PreToolUse --tool Bash --input '{"command":"ls"}'
```

`check` builds every handler and dry-runs one event without an agent in the loop; `run` is
what the agent calls. Both accept Claude Code's event names.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.