콘텐츠로 이동

Hooks

문제

CLAUDE.md에 “예외 없이 이렇게 해라”라고 적었는데 지켜지지 않습니다. 훅을 걸었더니 이번에는 프롬프트 자체가 막히는 사고가 납니다.

추천

반드시 지켜야 하는 규칙은 CLAUDE.md가 아니라 훅으로 옮깁니다. 단, UserPromptSubmit 훅은 실패 시 프롬프트 자체를 막으므로 실패를 흡수하는 가드부터 넣습니다.

이유

CLAUDE.md 문구는 권고이고 훅은 강제입니다. 이 차이가 훅을 쓰는 유일한 이유입니다. 자연어로 “이런 상황에서는 이 도구를 써라”라고 적어 둔 트리거는 잊히기 쉽습니다. 조건을 아무리 구체적으로 못박아도 사람이든 모델이든 매번 그 문구를 다시 읽지는 않기 때문입니다. 반드시 지켜져야 하는 규칙이면 산문으로 남기지 말고 훅으로 만듭니다.

UserPromptSubmit 훅이 0이 아닌 종료 코드로 끝나면 프롬프트 자체가 차단됩니다. 스크립트가 없는 머신에서 python3 없는파일.py를 실행하면 exit 2로 끝나고, 매 프롬프트가 막힙니다. [ -f "$f" ] && python3 "$f" || true 같은 가드가 필요합니다. 스크립트 내부도 전면 try/except + exit 0으로 막아야 합니다. 훅은 어떤 이유로도 사용자를 막아서는 안 됩니다.

입출력 계약은 고정돼 있습니다. stdin으로 {"prompt", "session_id"} JSON을 받고, stdout으로 {"hookSpecificOutput": {"hookEventName": "UserPromptSubmit", "additionalContext": "..."}}를 냅니다. 이 문자열이 그대로 모델 컨텍스트에 주입됩니다. 주입할 것이 없으면 아무것도 출력하지 않습니다. 이때 컨텍스트에 들어가는 토큰은 없습니다.

훅은 임의의 프로젝트에서 실행되므로 cwd를 신뢰하지 않습니다. 자원 경로는 Path(__file__).resolve().parent.parent로 잡고, env 변수와 $HOME/<repo>를 폴백으로 둡니다.

등록 전에 파이프로 직접 테스트합니다. 훅은 다음 턴에나 발동하므로, stdin을 직접 먹여 파일 있음/없음/무관 입력 세 분기를 미리 확인하는 편이 쌉니다. 등록 후에는 /hooks를 한 번 열거나 재시작해야 설정 워처가 읽습니다.

이벤트별 발동 조건도 다릅니다. UserPromptSubmit은 모든 프롬프트에서 돕니다. PostToolUseFailure는 도구 실패에만 발동합니다(성공한 작업이나 일반 프롬프트에는 실행되지 않습니다). Stop은 세션 종료 시 메시지를 주입합니다.

훅은 오탐을 낼 수 있습니다. 특정 명령을 정규식으로 잡아 막는 훅에서 자주 나는 문제가 있습니다. BSD sed(macOS)는 \b를 지원하지 않습니다. 쓰면 치환이 조용히 실행되지 않고 오탐이 그대로 남습니다. 단어 경계를 다른 방식으로 흉내 낸 대안도 예상하지 못한 문자열을 통과시킬 수 있습니다. 실패를 스크립트 안에 주석으로 남겨 두면 다음 사람이나 다음 세션이 같은 실수를 반복하지 않습니다.

예시

등록 전에 stdin을 직접 먹여 테스트합니다.

터미널 창
echo '{"prompt":"test","session_id":"abc"}' | python3 hook.py

가드 없는 훅이 만드는 사고를 막는 최소 형태:

#!/bin/bash
f="$HOME/.agents/context.py"
[ -f "$f" ] && python3 "$f" || true

출력 계약:

{"hookSpecificOutput": {"hookEventName": "UserPromptSubmit", "additionalContext": "..."}}

하지 말 것

가드 없이 UserPromptSubmit 훅에 스크립트를 바로 걸기. 스크립트가 없는 머신에서 매 프롬프트가 막히는 사고로 이어집니다.

cwd로 리소스 경로를 잡기. 훅은 임의의 프로젝트 디렉터리에서 실행됩니다.

CLAUDE.md 문구로 강제되길 기대하기. 반드시 지켜야 하는 규칙이면 애초에 훅 대상입니다.