가드 훅이 실패하면 막아야 할 명령이 왜 그대로 실행되나

동딩
2026.10.24·2분 읽기·

block을 두면 실패해도 명령이 막힌다

가드 훅 항목에 "onFailure": "block" 한 줄을 더하면 훅이 실패해도 명령이 막힙니다. v2.1.295부터 command·http 훅이 이 필드를 받습니다.

.claude/settings.jsonjson
{
"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막힘막힘
가드 훅이 실패한 방식에 따른 Bash 호출의 결과
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 두 줄을 이렇게 바꿉니다.

.claude/hooks/guard.shbash
CMD=$(jq -r .tool_input.command)
if [[ "$CMD" == *"push --force"* ]]; then
echo '{"systemMessage":"force push 주의"}'
exit 0
fi

exit 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 객체가 아니면 실패로 칩니다.

Comments

댓글

powered by giscus ↗