Hooks
Hooks (Settings, Hooks) run your own shell commands at set points in the agent loop. Each hook names an event and, for tool events, a matcher glob on the tool name. The other events match only an empty or * matcher.
Events
| Event | When | What it can do |
|---|---|---|
| PreToolUse | before a tool call runs | block the call, approve it (skipping the approval prompt), or rewrite its arguments |
| PostToolUse | after a tool call | append output to the tool result so the agent sees it (auto-format after every edit, run tests after a write) |
| UserPromptSubmit | when you submit a message, before the turn runs | block the prompt or inject extra context |
| SessionStart | once when a run begins | add context to the system prompt |
| Stop | when the agent would end its turn | force another turn with the reason fed back ("run the tests before you stop"), bounded so a hook can't keep the agent going forever |
| PreCompact | before the conversation is compacted | fold context into the material being summarized, so it survives compaction |
The exit-code contract
The simplest way to write a hook:
- a non-zero exit blocks the blocking events (PreToolUse, UserPromptSubmit, Stop)
- plain stdout and stderr are fed back to the agent
JSON output
For finer control, a hook prints a single JSON object on stdout:
| Field | Effect |
|---|---|
decision | "block" vetoes the action even on a zero exit; "approve" (PreToolUse only) skips the approval prompt. A block always wins over an approve. |
reason | a human-readable explanation, fed back to the agent (and shown with a block) |
additionalContext | extra context added where the event fires: the tool result (PreToolUse), your message (UserPromptSubmit), the system prompt (SessionStart), or the material being summarized (PreCompact) |
updatedInput | (PreToolUse only) replacement arguments the tool runs with |
systemMessage | a note for you, never added to the model's context |
Rewrites are re-checked. A rewrite that fails the tool's schema fails the call rather than falling back to the original arguments. Rewritten arguments are matched against your permission rules again, so the approval decision is made on what will actually run: a rewrite that lands on a deny rule is refused outright, even if the hook also approved. A hook can tighten policy, but it can never loosen a managed or project deny.
How hooks run
- Several matches run in order. Any block wins, and the last rewrite wins.
- Sandboxed: hooks run in the same project sandbox as
run_shell, with no network. - Failing safe: a hook that can't be started counts as failed, so a PreToolUse or UserPromptSubmit hook that can't run blocks rather than lets the action through.
Environment variables
| Variable | Holds |
|---|---|
$HOUSTON_HOOK_EVENT | the event name |
$HOUSTON_TOOL_NAME | the tool name |
$HOUSTON_TOOL_INPUT | the tool's arguments |
$HOUSTON_TOOL_RESULT | the tool result (PostToolUse) |
$HOUSTON_USER_PROMPT | your message (UserPromptSubmit) |
Large payloads. A payload over 16 KB (a large write, a long tool output, a long pasted prompt) is too big for an environment variable, so it's written to a private temp file instead. The variable is left unset, and $HOUSTON_TOOL_INPUT_FILE, $HOUSTON_TOOL_RESULT_FILE, or $HOUSTON_USER_PROMPT_FILE holds the file's path. A hook that may see large payloads should read whichever of the two is set. The file is removed once the hooks finish.