vigiles API reference
    Preparing search index...

    Interface HookRunResult

    What a program did: its exit, its output, and (when confined) its effects.

    interface HookRunResult {
        blocked: boolean;
        blockedBy: readonly BlockMechanism[];
        conditionReason: string;
        decision: "approve" | "block" | "allow" | "deny" | "ask" | undefined;
        delivery: HookDelivery;
        egress: readonly EgressAttempt[];
        egressDropped?: { bytes: number; packets: number };
        exitCode: number;
        filesWritten?: readonly string[];
        haltsTurn: boolean;
        json: HookOutput | null;
        ran: boolean;
        stderr: string;
        stdout: string;
    }

    Hierarchy (View Summary)

    Index

    Properties

    blocked: boolean

    Normalized 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.

    blockedBy: readonly BlockMechanism[]

    Every 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.

    conditionReason: string

    Why the hook ran or did not — the condition verdict, always present.

    decision: "approve" | "block" | "allow" | "deny" | "ask" | undefined

    The decision the hook expressed, preferring the structured permissionDecision ("allow"|"deny"|"ask") then legacy decision ("approve"|"block"), else undefined.

    delivery: HookDelivery

    Whether 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.

    egress: readonly EgressAttempt[]

    Network 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.

    egressDropped?: { bytes: number; packets: number }

    Allowlisted-egress mode only: aggregate off-allowlist traffic the packet wall dropped. Undefined when RunScriptOptions.egress was unset.

    exitCode: number

    Exit code. A signal-killed run reports 1.

    filesWritten?: readonly string[]

    Files 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.

    haltsTurn: boolean

    The 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.

    json: HookOutput | null

    Parsed stdout JSON if the hook emitted a JSON decision, else null.

    ran: boolean

    Whether 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.

    stderr: string

    Everything the program wrote to fd 2. Advisory output usually lives HERE — a runner that reads only stdout silently discards it.

    stdout: string