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):
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
--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
PostToolUsehook 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 runmeters what the rule would have rewritten and reports it on stderr (rewrite reached nobody) and asinbound_rewrite_unreachable: trueundergateway. run_endsees the last assistant turn, read from the agent’s transcript when it passestranscript_path; acommandhandler scans a report the agent wrote to a file.
Checking before wiring
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.