Summary
The ai: integration (2.1.10, #1448) generates .claude/settings.json entries that run lefthook run <hook>. Claude Code only treats a hook as blocking when the command exits with code 2: for Stop, exit 2 blocks the stop and feeds stderr back to the agent so it keeps working; for PreToolUse, exit 2 blocks the tool call. Any other non-zero exit is "non-blocking": stderr is shown to the user and the agent stops / the tool call proceeds.
lefthook run exits 1 whenever a job fails, even when the job itself exits 2. So a Stop: validate hook that fails lint just prints to the user after the agent has already finished; the agent never sees it.
Reproduction
# lefthook.yml
ai:
claude:
Stop: validate
validate:
jobs:
- run: exit 2
$ lefthook install
$ lefthook run validate; echo $?
...
exit status 2
...
1
Same with run: exit 1 → lefthook exits 1. Claude Code docs on exit codes: https://code.claude.com/docs/en/hooks#hook-output
Suggestion
When invoked from a generated agent hook (or via a flag, e.g. lefthook run --agent validate that the ai: generator emits), exit 2 on failure — or propagate the highest job exit code. Also worth noting in the docs that Claude passes stop_hook_active: true on stdin when the agent is already continuing from a Stop hook; users who lint on Stop will want to exit 0 in that case to avoid a loop.
lefthook 2.1.10, macOS (Homebrew).
Summary
The
ai:integration (2.1.10, #1448) generates.claude/settings.jsonentries that runlefthook run <hook>. Claude Code only treats a hook as blocking when the command exits with code 2: forStop, exit 2 blocks the stop and feeds stderr back to the agent so it keeps working; forPreToolUse, exit 2 blocks the tool call. Any other non-zero exit is "non-blocking": stderr is shown to the user and the agent stops / the tool call proceeds.lefthook runexits 1 whenever a job fails, even when the job itself exits 2. So aStop: validatehook that fails lint just prints to the user after the agent has already finished; the agent never sees it.Reproduction
Same with
run: exit 1→ lefthook exits 1. Claude Code docs on exit codes: https://code.claude.com/docs/en/hooks#hook-outputSuggestion
When invoked from a generated agent hook (or via a flag, e.g.
lefthook run --agent validatethat theai:generator emits), exit 2 on failure — or propagate the highest job exit code. Also worth noting in the docs that Claude passesstop_hook_active: trueon stdin when the agent is already continuing from a Stop hook; users who lint on Stop will want to exit 0 in that case to avoid a loop.lefthook 2.1.10, macOS (Homebrew).