Hooks로 품질 게이트 자동화: 포맷·린트·테스트·위험 차단
훅은 '권장사항'을 '강제사항'으로 바꾼다
CLAUDE.md나 프롬프트에 규칙을 적어두면, 대부분의 경우 Claude는 잘 따릅니다. 하지만 세션이 길어지거나 작업이 복잡해지면, 중요한 규칙이 뒤로 밀릴 때가 있습니다. 이때 훅(Hooks)은 규칙을 "기억해야 하는 것"에서 "자동으로 실행되는 것"으로 바꿔 줍니다.12
Claude Code의 훅은 특정 시점에 자동으로 실행되는 사용자 정의 명령, HTTP 호출, 프롬프트, 에이전트입니다. 즉, 모델이 알아서 해주길 기대하는 대신 반드시 일어나야 하는 동작을 시스템에 고정하는 장치입니다.1
이 챕터의 목표는 세 가지입니다.
어떤 이벤트에서 훅을 걸 수 있는지 이해하고
포맷·린트·테스트·위험 차단 같은 품질 게이트를 어디에 두어야 하는지 감을 잡고
팀이 실제로 유지할 수 있는 수준으로 훅을 설계하는 것입니다.
훅을 바라보는 기본 관점: 이벤트 × 매처 × 핸들러
Claude Code의 훅은 크게 세 층으로 이해하면 됩니다.1
이벤트(Event): 언제 실행할 것인가
매처(Matcher): 어떤 경우에만 실행할 것인가
핸들러(Handler): 실제로 무엇을 실행할 것인가
이 구조를 이해하면 훅 설정이 복잡하게 느껴지지 않습니다. "언제, 무엇에 대해, 무슨 행동을" 자동화할지를 정하는 일이라고 보면 됩니다.
어떤 시점에 훅을 거는가: 자주 쓰는 이벤트 6개
공식 문서에는 다양한 이벤트가 있지만, 일상적인 품질 게이트 자동화에서는 아래 여섯 개만 잘 알아도 충분합니다.12
1) PreToolUse
도구가 실행되기 직전에 실행됩니다. 가장 대표적인 용도는 위험한 명령 차단입니다.1
예:
rm -rf같은 파괴적 명령 차단민감한 파일 수정 차단
특정 브랜치에서의 직접 커밋 차단
2) PostToolUse
도구가 성공적으로 실행된 직후 실행됩니다.1
예:
파일 수정 후 자동 포맷
특정 도구 실행 후 로그 기록
편집 직후 가벼운 검증 실행
3) PostToolUseFailure
도구가 실패한 직후 실행됩니다.1
예:
실패한 테스트 명령을 별도 로그로 남기기
특정 종류의 실패를 감지해 디버그 힌트 출력
4) SessionStart
세션이 시작되거나 재개될 때 실행됩니다. compact, resume, clear 같은 시작 경로도 구분할 수 있습니다.12
예:
압축(compact) 뒤에 꼭 기억해야 할 프로젝트 규칙 재주입
세션 시작 시 최근 커밋이나 현재 스프린트 정보 주입
5) PermissionRequest
권한 대화상자가 뜨려는 순간 실행됩니다.12
예:
특정 안전한 권한 요청만 자동 승인
반대로 민감한 요청은 항상 사용자 확인 유지
6) Stop
Claude가 응답을 마치고 멈추려는 시점에 실행됩니다.1
예:
세션 종료 직전 품질 게이트 검사
마지막으로 테스트/타입체크가 통과했는지 확인
다만 Stop 훅은 무거워지기 쉽습니다. 그래서 "마지막에 다 검사"보다, 작업 중간은 가볍게 자동 교정하고 마지막에는 빠른 확인만 하는 구조가 실전에서 더 오래 갑니다.
매처: 훅을 '필요한 경우에만' 실행하게 만드는 장치
좋은 훅의 핵심은 많이 거는 것이 아니라, 정확히 거는 것입니다. 훅은 생각보다 자주 실행됩니다. 매처 없이 넓게 걸면 체감 성능이 급격히 떨어질 수 있습니다.1
예를 들어 PostToolUse에 포맷 훅을 걸고 싶다면, 모든 도구가 아니라 파일 편집 도구에만 반응하도록 제한하는 편이 좋습니다.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
}
]
}
]
}
}여기서 Edit|Write는 정규식 매처입니다. 즉, Bash, Read, MCP 도구 호출 등에는 반응하지 않습니다.12
실전 규칙은 단순합니다.
포맷/가벼운 검증:
Edit|Write위험 명령 차단:
Bash설정 변경 감사:
ConfigChange알림:
Notification
무조건 넓게 잡지 말고, 먼저 좁게 시작한 뒤 필요한 경우만 넓히는 편이 안전합니다.
핸들러 유형: command부터 시작하면 충분하다
공식 문서상 훅 핸들러는 네 종류가 있습니다.1
command: 셸 명령 실행http: HTTP 엔드포인트로 JSON POSTprompt: 모델에게 단일 턴 평가 요청agent: 서브에이전트 기반 다단계 검증
하지만 대부분의 팀은 command만으로도 상당히 많은 자동화를 구현할 수 있습니다. 이 장도 실전성과 유지보수성을 위해 command 훅 중심으로 설명합니다.
이유는 간단합니다.
로컬 도구와 바로 연결하기 쉽고
동작이 결정적이며
디버깅이 비교적 쉽기 때문입니다.
판단이 필요한 보안 정책, 복잡한 조건 평가가 정말 필요해질 때만 prompt나 agent 훅을 검토해도 늦지 않습니다.12
훅의 입력과 출력: stdin JSON, stdout/stderr, exit code
Command 훅은 JSON 입력을 stdin으로 받고, stdout/stderr와 종료 코드로 결과를 전달합니다.12
입력
대표적으로 자주 보는 필드는 다음입니다.1
hook_event_namesession_idcwdtool_nametool_inputpermission_mode
예를 들어 Bash 실행 직전의 PreToolUse 훅은 대체로 이런 정보를 받습니다.
{
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "npm test"
}
}출력과 종료 코드
가장 기본적인 동작은 세 가지입니다.12
exit 0: 허용
exit 2: 차단
그 외 종료 코드: 동작은 계속되지만 stderr는 로그로만 남음
중요한 점은 이벤트마다 제어 방식이 조금씩 다르다는 것입니다.
PreToolUse는 JSON으로permissionDecision을 반환해allow/deny/ask같은 세밀한 제어가 가능PostToolUse,Stop,ConfigChange등은 top-leveldecision: "block"형태를 사용SessionStart,UserPromptSubmit는 stdout에 쓴 텍스트를 Claude 컨텍스트에 추가할 수 있음
즉, "훅은 전부 같은 방식으로 JSON을 돌려준다"가 아니라, 이벤트별로 반환 형식이 다르다고 이해하는 편이 정확합니다.1
레시피 1: 파일 수정 직후 자동 포맷
가장 효과가 큰 훅은 역시 자동 포맷입니다. 포맷은 사람이 잊기 쉽고, Claude도 항상 실행하지는 않습니다. 그런데 포맷이 한 번만 어긋나도 리뷰가 지저분해집니다.
공식 가이드의 가장 단순한 예시는 다음과 같습니다.2
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write"
}
]
}
]
}
}이 패턴의 장점은 분명합니다.
바뀐 파일만 포맷하므로 범위가 작고
전체 리포를 다시 포맷하지 않아 불필요한 diff를 줄이며
Claude가 편집할 때마다 스타일이 자동으로 맞춰집니다.
실전에서는 프로젝트 언어에 맞게 포맷터를 나누어도 좋습니다. 다만 처음부터 너무 많은 언어를 넣기보다, 팀에서 가장 자주 쓰는 파일 타입부터 시작하는 편이 유지보수에 유리합니다.
레시피 2: 보호 파일 수정 차단
민감한 파일은 "조심하자"보다 "막자"가 낫습니다. 예를 들어 .env, 잠금 파일, .git/ 내부 파일 같은 것은 실수 한 번의 비용이 큽니다.
공식 가이드도 보호 파일 차단을 대표 예시로 소개합니다.2
핵심은 PreToolUse에서 파일 경로를 검사하고, 보호 대상이면 차단하는 것입니다.
예시 스크립트의 구조는 대체로 이렇습니다.
#!/bin/bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
case "$FILE_PATH" in
*.env|.env|.env.*|*/.git/*|package-lock.json)
echo "Blocked: protected file cannot be modified" >&2
exit 2
;;
esac
exit 0이 훅이 유용한 이유는, Claude에게 사후에 "그 파일은 건드리지 말았어야 했어"라고 말하는 대신 애초에 변경이 일어나지 않게 만들기 때문입니다.
레시피 3: 위험한 Bash 명령 차단
실무에서 가장 민감한 훅은 Bash 가드입니다. rm -rf, 원격 스크립트 파이프 실행, 메인 브랜치에서의 직접 커밋 같은 것은 한 번의 실수로 비용이 커질 수 있습니다.
공식 문서의 대표 예시도 PreToolUse에서 rm -rf를 막는 형태입니다.1
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/block-rm.sh"
}
]
}
]
}
}스크립트는 JSON을 읽고, 명령 문자열에 따라 허용/거부를 결정합니다.1
#!/bin/bash
COMMAND=$(jq -r '.tool_input.command')
if echo "$COMMAND" | grep -q 'rm -rf'; then
jq -n '{
hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "Destructive command blocked by hook"
}
}'
else
exit 0
fi이 방식의 장점은 단순 차단을 넘어, 왜 막혔는지 Claude에게 피드백을 줄 수 있다는 점입니다. 그래서 Claude가 같은 실수를 반복할 가능성이 줄어듭니다.
레시피 4: 압축 후 컨텍스트 재주입
긴 세션을 운영하다 보면 compact 뒤에 중요한 규칙이 희미해질 때가 있습니다. 이럴 때는 SessionStart 이벤트를 compact 매처와 함께 쓰면 유용합니다.2
예:
{
"hooks": {
"SessionStart": [
{
"matcher": "compact",
"hooks": [
{
"type": "command",
"command": "echo 'Reminder: use Bun, not npm. Run bun test before committing. Current sprint: auth refactor.'"
}
]
}
]
}
}이 훅은 압축 이후 stdout 텍스트를 컨텍스트에 다시 넣어 줍니다.2
다만 이 용도는 남용하면 안 됩니다. 너무 많은 문장을 다시 주입하면 compact의 이점이 줄어듭니다. 정말 자주 잊히면 위험한 규칙만 짧게 남기는 편이 좋습니다.
레시피 5: 특정 권한 요청만 자동 승인
매번 동일한 안전한 권한 요청을 수동 승인하는 것이 피로하다면 PermissionRequest 훅을 쓸 수 있습니다.12
공식 가이드에서는 ExitPlanMode를 자동 승인하는 예시를 보여 줍니다.2
{
"hooks": {
"PermissionRequest": [
{
"matcher": "ExitPlanMode",
"hooks": [
{
"type": "command",
"command": "echo '{\"hookSpecificOutput\": {\"hookEventName\": \"PermissionRequest\", \"decision\": {\"behavior\": \"allow\"}}}'"
}
]
}
]
}
}하지만 이 기능은 매우 좁게 써야 합니다. 매처를 넓게 잡으면 파일 수정, 쉘 실행 같은 민감한 권한 요청까지 자동 승인될 수 있기 때문입니다.2
실전 원칙은 이렇습니다.
자동 승인 대상은 정말 반복적이고 안전한 것만
매처는 가능한 한 구체적으로
광범위한 권한 자동 승인은 피한다
설정 위치: 팀 공유와 개인 실험을 분리하라
훅은 여러 위치에 둘 수 있습니다.1
~/.claude/settings.json: 내 모든 프로젝트.claude/settings.json: 특정 프로젝트, 팀 공유 가능.claude/settings.local.json: 특정 프로젝트, 개인 전용관리형 정책: 조직 차원 제어
플러그인/스킬/에이전트 내부 설정
실무에서는 보통 이렇게 나누는 편이 좋습니다.
프로젝트 설정
팀 모두가 따라야 하는 규칙:
보호 파일 차단
자동 포맷
공용 테스트/품질 게이트
로컬 설정
개인 실험이나 개인 알림:
데스크톱 알림
개인 로그 수집
아직 팀 합의가 없는 실험적 훅
이렇게 나누면 팀 규칙은 안정적으로 공유되고, 개인 실험은 충돌 없이 진행할 수 있습니다.
Claude 훅, Git 훅, CI 게이트의 역할 분담
훅 설계에서 가장 흔한 실수는 하나의 계층에 모든 걸 몰아넣는 것입니다. 하지만 실제로는 세 계층이 역할을 나눌 때 가장 강합니다.
Claude 훅
작업 중간의 자동 교정과 차단에 강합니다.
편집 직후 포맷
위험 명령 차단
세션 중 컨텍스트 재주입
Git 훅
커밋 직전의 빠른 검증에 적합합니다.
스테이징 파일 대상 lint-staged
pre-commit 검사
커밋 메시지 규칙
CI
머지 전 강한 검증에 적합합니다.
전체 테스트
보안 스캔
품질 게이트
대규모 통합 검증
요약하면:
Claude 훅은 에이전트가 작업하는 중간을 지키고
Git 훅은 커밋 시점을 지키며
CI는 팀 저장소에 합쳐지기 전 마지막 관문을 지킵니다.
이 셋을 섞지 말고 계층화해야, 빠르면서도 지치지 않는 자동화가 됩니다.
운영 원칙: 훅은 '많이'보다 '작게, 결정적으로'
좋은 훅은 보통 작습니다. 그리고 실패했을 때 이유가 분명합니다.
1) 먼저 자동 교정부터
가장 먼저 넣을 훅은 자동 포맷처럼 위험이 낮고 효과가 큰 것입니다.
2) 그다음 사고 방지
파괴적 명령, 민감 파일 수정처럼 한 번의 실수가 큰 것만 차단합니다.
3) 무거운 검증은 신중하게
테스트, 타입체크를 매번 과하게 돌리면 결국 훅을 끄고 싶어집니다. 자주 필요한 빠른 검증만 훅에 넣고, 무거운 검증은 Git 훅이나 CI로 넘기는 편이 낫습니다.
4) 훅도 디버깅 가능한 구조로
스크립트는 짧게 유지하고, 가능한 한 절대 경로나 $CLAUDE_PROJECT_DIR를 이용해 참조하세요.1 훅이 복잡해질수록 "안 되는 이유"를 찾기 어려워집니다.
팀용 체크리스트: 어떤 순서로 도입할까
팀에 훅을 처음 도입할 때는 아래 순서가 무난합니다.
PostToolUse에 자동 포맷 추가PreToolUse에 보호 파일/위험 Bash 차단 추가필요하면
SessionStart(compact)에 짧은 규칙 재주입좁은 범위의
PermissionRequest자동 승인 추가무거운 검증은 훅보다 Git 훅/CI에 우선 배치
이 순서를 따르면, 효과는 빠르게 얻고 유지보수 비용은 과도하게 키우지 않을 수 있습니다.
정리: 훅의 목적은 '자동화'가 아니라 '예측 가능성'이다
훅을 도입하는 진짜 이유는 단순히 손을 덜 쓰기 위해서가 아닙니다. 항상 같은 규칙이 같은 순간에 적용되게 만드는 것, 즉 예측 가능성을 높이기 위해서입니다.
이 장의 결론은 명확합니다.
포맷은 PostToolUse로 자동화한다.
위험 명령과 보호 파일은 PreToolUse로 차단한다.
압축 뒤 잊기 쉬운 정보만 SessionStart로 짧게 재주입한다.
권한 자동 승인은 아주 좁게 쓴다.
무거운 검증은 Git 훅과 CI로 넘겨 계층화한다.
이 구조가 자리 잡히면, Claude Code는 "가끔 규칙을 잊는 똑똑한 동료"가 아니라 규칙을 시스템적으로 지키는 개발 파이프라인의 일부가 됩니다.
참고
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.