Problem
Hooks currently fail open in every failure mode: if the hook script fails to spawn, crashes, times out, or exits with a code other than 0 or 2, the CLI resolves the result to allow and the gated operation proceeds. This is the right default for notifications and lightweight checks, and the docs are honest about the consequence: hooks "should not be used as the sole security barrier."
The gap is that for exactly the use case the docs call out (a PreToolUse hook gating dangerous shell commands), fail-open means the gate silently stops gating the moment the hook script breaks, hangs, or is removed. The agent sees no error and the user gets no signal that their guard is gone. A policy engine wired in through hooks cannot currently be made reliable, no matter how reliable the hook script itself is, because the runner's failure paths always allow.
Proposal
Add an optional per-hook fail_mode field to [[hooks]]:
"open" (default): today's behavior, unchanged. Existing configs are unaffected.
"closed": any failure to deliver a verdict (spawn failure, runtime error, timeout, exit code other than 0 or 2) blocks the operation, with an explicit reason string so the model can react.
Semantics that stay the same in both modes: exit 0 allows, exit 2 blocks with the stderr reason, structured stdout permissionDecision works as documented, and a user interrupt (Esc) resolves to allow, since the turn is being cancelled anyway.
One related edge: hook deduplication collapses identical (cwd, command) entries to a single run. With mixed modes on identical commands, the surviving entry should be the stricter one, otherwise config order silently weakens a gate.
Implementation
I have this working, with tests, on a branch: https://github.com/StressTestor/kimi-code/tree/feat/hook-fail-mode
It covers both agent-core (session/hooks) and agent-core-v2 (agent/externalHooks), including the config-shape difference between them (hook arrays reach agent-core's schema in raw snake_case because transformTomlData passes arrays through, while agent-core-v2's hooksFromToml camelizes entries before validation). Docs updated in en and zh, changeset included, verified end to end against a built CLI (a crashing PreToolUse hook with fail_mode = "closed" blocks the Bash call; the same hook without it fails open).
Happy to open the PR if this direction works for you.
Problem
Hooks currently fail open in every failure mode: if the hook script fails to spawn, crashes, times out, or exits with a code other than 0 or 2, the CLI resolves the result to allow and the gated operation proceeds. This is the right default for notifications and lightweight checks, and the docs are honest about the consequence: hooks "should not be used as the sole security barrier."
The gap is that for exactly the use case the docs call out (a PreToolUse hook gating dangerous shell commands), fail-open means the gate silently stops gating the moment the hook script breaks, hangs, or is removed. The agent sees no error and the user gets no signal that their guard is gone. A policy engine wired in through hooks cannot currently be made reliable, no matter how reliable the hook script itself is, because the runner's failure paths always allow.
Proposal
Add an optional per-hook
fail_modefield to[[hooks]]:"open"(default): today's behavior, unchanged. Existing configs are unaffected."closed": any failure to deliver a verdict (spawn failure, runtime error, timeout, exit code other than 0 or 2) blocks the operation, with an explicit reason string so the model can react.Semantics that stay the same in both modes: exit 0 allows, exit 2 blocks with the stderr reason, structured stdout
permissionDecisionworks as documented, and a user interrupt (Esc) resolves to allow, since the turn is being cancelled anyway.One related edge: hook deduplication collapses identical
(cwd, command)entries to a single run. With mixed modes on identical commands, the surviving entry should be the stricter one, otherwise config order silently weakens a gate.Implementation
I have this working, with tests, on a branch: https://github.com/StressTestor/kimi-code/tree/feat/hook-fail-mode
It covers both
agent-core(session/hooks) andagent-core-v2(agent/externalHooks), including the config-shape difference between them (hook arrays reach agent-core's schema in raw snake_case becausetransformTomlDatapasses arrays through, while agent-core-v2'shooksFromTomlcamelizes entries before validation). Docs updated in en and zh, changeset included, verified end to end against a built CLI (a crashing PreToolUse hook withfail_mode = "closed"blocks the Bash call; the same hook without it fails open).Happy to open the PR if this direction works for you.