가드 훅이 실패하면 막아야 할 명령이 왜 그대로 실행되나
block을 두면 실패해도 명령이 막힌다
가드 훅 항목에 "onFailure": "block" 한 줄을 더하면 훅이 실패해도 명령이 막힙니다. v2.1.295부터 command·http 훅이 이 필드를 받습니다.
{ "hooks": { "PreToolUse": [{ "matcher": "Bash", "hooks": [{ "type": "command", "command": "bash .claude/hooks/guard.sh", "onFailure": "block" }] }] }}guard.sh가 아래처럼 실패하면 Claude Code가 그 Bash 호출을 막습니다. 훅이 exit 2로 거부했을 때와 결과가 같습니다.
- 뜨지 못할 때
- 0·2 말고 다른 코드로 끝날 때
timeout을 넘길 때
설정이 먹었는지 보려면 스크립트 이름을 잠깐 바꾸고 Claude에게 echo ran을 시킵니다. 명령 대신 failed; blocking because onFailure is "block"이 뜨면 됩니다.
비워 둔 훅은 경고 한 줄만 남긴다
onFailure를 비워 둔 훅은 실패해도 화면에 hook error 한 줄만 남기고 명령을 그대로 돌립니다. guard.sh를 scripts/로 옮기고 settings.json의 경로는 그대로 둔 저장소에서, 비워 둔 쪽과 block을 넣은 쪽에 echo ran을 시켰습니다(v2.1.310).
onFailure를 비워 두면 기본값 "continue"로 동작해, 못 뜬 훅을 논블로킹 오류로 처리합니다.
비워 둔 훅은 죽거나 시간을 넘겨도 호출을 막지 않습니다. PreToolUse 훅이 timeout에 걸리면 보통의 권한 확인으로 넘어갑니다.
| 비워 둠 | block | |
|---|---|---|
| 스크립트 없음 | 실행 | 막힘 |
| exit 1로 죽음 | 실행 | 막힘 |
| timeout 초과 | 실행 | 막힘 |
| JSON 깨짐 | 실행 | 막힘 |
| exit 2 | 막힘 | 막힘 |
Windows에서 PowerShell로 가드를 짠 사람
Git Bash가 없는 Windows에서는 셸 형식 PreToolUse·PermissionRequest 훅이 실패하면 onFailure를 비워 둬도 호출이 막힙니다. Git for Windows를 깔아 Claude Code가 Git Bash를 찾으면 이 동작이 사라집니다. "onFailure": "block"과 "shell": "powershell"을 함께 넣으면 Git Bash가 있든 없든 PowerShell로 돌고, 실패하면 막힙니다.
경고용 exit 1은 exit 0으로 바꾼다
block을 넣으면 exit 1도 실패로 칩니다. git push --force에 경고만 하려고 그 코드를 쓰던 훅은 명령을 막기 시작합니다. 경고 문구는 stdout JSON의 systemMessage 필드에 담고 exit 0으로 끝냅니다. 이 필드에 담은 글은 사용자 화면에 뜹니다. 경고만 하던 echo … >&2와 exit 1 두 줄을 이렇게 바꿉니다.
CMD=$(jq -r .tool_input.command)if [[ "$CMD" == *"push --force"* ]]; then echo '{"systemMessage":"force push 주의"}' exit 0fiexit 0으로 바꾼 훅은 block 아래서도 명령을 통과시키고 force push 주의를 띄웁니다. JSON에 permissionDecision: "allow"를 적어도 종료 코드가 0이 아니면 실패로 칩니다.
Stop 훅·async 훅·HTTP 훅을 쓰는 사람
Stop·SubagentStop·TaskCompleted·TeammateIdle 훅에는 onFailure가 듣지 않습니다. 이 이벤트의 exit 2는 Claude에게 일을 더 시키는 신호라서, Claude가 고칠 수 없는 고장 난 훅에는 맞지 않습니다. async·asyncRewake로 띄운 훅에도 듣지 않습니다. HTTP 훅은 연결이 안 되거나 응답이 2xx가 아니면 실패입니다. 2xx라도 본문이 비어 있지 않은데 JSON 객체가 아니면 실패로 칩니다.