ReadonlyblockedNormalized decision: the dangerous call did NOT go through. Set by exit 2,
decision:"block", permissionDecision:"deny", or the harness's
halt-the-turn field (Claude Code {"continue": false} — see
HookRunResult.haltsTurn).
The halt case was missing until 2026-08-31 (#174), and the shape of that bug
is worth keeping written down: verifyGuardrail reads this field, so a real
PreToolUse guard that stopped every one of the disaster battery's commands
was reported by assertBlocksDisasters as blocking NONE of them. A tool
whose stated job is catching a guard that looks fine and silently does
nothing said the opposite about a working guard — the same false-confidence
failure, with the sign flipped.
ReadonlyblockedEvery block mechanism that fired, from the closed BlockMechanism
set. Reported so the next mechanism needs no new boolean on this interface —
haltsTurn exists because the halt case was added as a one-off, and this is
the shape that stops the pattern repeating.
ReadonlyconditionWhy the hook ran or did not — the condition verdict, always present.
ReadonlydecisionThe decision the hook expressed, preferring the structured
permissionDecision ("allow"|"deny"|"ask") then legacy decision
("approve"|"block"), else undefined.
ReadonlydeliveryWhether what the hook PRINTED reached the MODEL, given its event, its exit
code and which stream carried the text — delivered / undelivered /
blocked / silent / unknown.
🔴 READ THIS, NOT stderr, WHEN TESTING A NUDGE. A PostToolUse hook that
writes to stderr and exits 0 delivers NOTHING ("Claude never sees stderr
from a hook that exits 0" — the host's hooks docs), yet a test asserting on
r.stderr is green. Measured 2026-10-11: a downstream linter shipped that
way for weeks. assertDelivered(r) fails on it and names the cure.
ReadonlyegressNetwork egress the run attempted. With RunScriptOptions.recordEgress: every (blocked) attempt the proxy saw. With RunScriptOptions.egress: the allowlisted hosts actually reached, with packet counts. Empty otherwise.
Optional ReadonlyegressAllowlisted-egress mode only: aggregate off-allowlist traffic the packet wall dropped. Undefined when RunScriptOptions.egress was unset.
ReadonlyexitExit code. A signal-killed run reports 1.
Optional ReadonlyfilesFiles written to the work dir (relative paths). undefined when writes
were never recorded — a different fact from [] ("recorded, wrote
nothing"). Recording diffs the work dir before and after, which only a
CONFINED run does. assertNoWrite / assertWroteOnly refuse undefined
rather than pass having inspected nothing.
ReadonlyhaltsThe hook halted the WHOLE TURN rather than denying one call — the harness's
haltsTurnField came back false.
Reported separately because it is strictly stronger than a deny and the two
are worth telling apart when a test asks WHICH mechanism fired. blocked
stays the question nearly every caller means ("did the action happen?"), so
a halt sets both.
ReadonlyjsonParsed stdout JSON if the hook emitted a JSON decision, else null.
ReadonlyranWhether the hook process was actually SPAWNED. False only when a declared
condition (RunHookOptions.condition) did not match, meaning the harness
would not have run it either.
🔴 READ THIS BEFORE READING blocked. ran: false and a hook that ran and
allowed both arrive as blocked: false, and they are completely different
facts — "the harness would never invoke this guard here" versus "the guard
looked and let it through". Collapsing them is what let a conditional guard
be reported as blocking a battery it cannot see.
ReadonlystderrEverything the program wrote to fd 2. Advisory output usually lives HERE — a runner that reads only stdout silently discards it.
Readonlystdout
What a program did: its exit, its output, and (when confined) its effects.