Hooks and trust hooks
Host hooks are external commands that observe (and, for two events, gate)
session activity. They are configured under settings.hooks and run by the
HostHooks runtime (crates/pi-coding/src/hooks.rs). Extensions additionally
receive lifecycle events — including a trust_decision event that can
recommend approving a tentative project trust decision.
Host hooks
{
"hooks": [
{ "event": "pre_tool_call", "matcher": "read", "command": ["/opt/hooks/guard", "--strict"], "timeoutMs": 1200, "failClosed": true },
{ "event": "session_end", "command": ["/opt/hooks/bye"] }
]
}
HookConfig fields (settings.rs:731-757):
| Field | Meaning |
|---|---|
event | pre_tool_call, post_tool_call, session_start, session_end, turn_start, turn_end, or pre_trust_decision. Unknown events are rejected at deserialize time. |
matcher | Exact or substring match on the event subject (tool name, message role, or canonical project path). Absent matchers fire for every subject. |
command | Command argv run without a shell (command[0] is the executable, the rest are argv); must be non-empty. |
timeoutMs | Per-hook timeout; defaults to 5000, capped at 60000. A timed-out hook's process group is killed. |
enabled | Set false to skip the entry without removing it. |
failClosed | Only meaningful for pre_tool_call and pre_trust_decision: when the hook errors or times out, fail closed (block the tool / deny the trust decision) instead of the default fail-open (allow). |
Hooks are external commands run without a shell. The event payload is
written as JSON on stdin; stdout (capped) is parsed as JSON for
pre_tool_call and pre_trust_decision decisions. The envelope carries
event, cwd, sessionId, timestamp, and subject — no secrets.
Blocking semantics
Only pre_tool_call and pre_trust_decision can block: a
{"decision":"block","reason":"..."} response prevents the tool from running
or denies the tentative trust decision. Every other event is advisory
(logged). Hook failures (spawn error, non-zero exit, timeout, malformed JSON)
fail open for the two blocking events unless the entry sets
failClosed: true, in which case the tool is blocked (or the trust decision
denied) instead.
pre_tool_callpayloads include the toolnameand a text rendering ofarguments.pre_trust_decisionpayloads carry the canonical projectpath, the tentativedecision(trusted/untrusted/ask), andisNew— the same spelling the extensiontrust_decisionevent uses (hooks.rs:333-351).
Host approval runs before the existing host hooks and extension reducers; a denial skips later hooks.
Extension trust hook
Extensions registered for the event_hooks capability receive a
trust_decision event ({path, decision, isNew}) and may recommend approval
with {approve: true} (ExtensionTrustDecisionReduction in
extensions.rs:2187-2196). The contract is fail-open by design:
- The event never carries a deny surface — an extension can only recommend approval.
- The recommendation can only upgrade an undecided (
ask) tentative decision to trusted; the host applies it viacrate::trust::apply_trust_hook_outcomes, so a stored denial is never weakened.
Project-trust extensions also receive a project_trust event
(ExtensionProjectTrustReduction), and untrusted project extension manifests
are refused at load/execute time (extensions.rs:413-418, 623-625).
Where hooks are applied
- Session lifecycle:
session_start/session_endfire around the session. - Turn lifecycle:
turn_start/turn_endfire around each agent turn. - Tool lifecycle:
pre_tool_call/post_tool_callobserve (and gate) each tool invocation; the post hook observes the final result after extension reduction, and its output never mutates the tool result (except for doom-loop recovery, which replaces the result with an actionable stop message — seesession-recovery.md). - Trust:
pre_trust_decisionfires for a tentative trust decision before the stored decision is consulted/recorded.
Invariants
- Hooks never run through a shell; argv is passed directly.
- Payloads are bounded (stdout capped), redacted (no secrets), and carry a millisecond timestamp.
- Blocking is opt-in per event: only
pre_tool_callandpre_trust_decisioncan block, and only with an explicitfailClosedentry do errors turn into blocks. - Disabled entries never fire; empty commands are skipped with a diagnostic.
- Extension trust recommendations can only upgrade an
askto trusted — never weaken a stored decision.
Related documentation
security.md— approval modes, trust boundaryextensions.md— extension lifecycle events and capabilitiessettings-trust.md—hookssettings and trust resolution