Claude Code로 배포 방식을 바꾸는 5가지 훅
출처: Dev.to
5월 22일에 공개된 dotfiles 글에서는 개인 AI 스택의 한 구성 요소로서 hooks를 언급하고 넘어갔습니다. 하지만 hooks는 단순히 지나칠 수 없는 중요한 요소입니다. hooks는 **맛(taste)**을 코드로 바꾸는 원시적인 메커니즘으로, AI 작업을 위한 편집기의 자동 포맷‑온‑세이브와 같은 역할을 하며, 여러분이 아니라 에이전트의 행동에 적용됩니다.
Anthropic의 hooks 문서에는 8가지 이벤트 타입이 나와 있습니다. 대부분의 공개 예제는 그 중 하나만 연결해 작은 안전 검사를 시연하고 끝냅니다. 실제 가치는 올바른 hook을 올바른 불변식(invariant)과 짝지었을 때 비로소 나타납니다. 아래는 제가 실제로 사용하고 있는 다섯 가지 hook과, 각각이 강제하는 구체적인 불변식입니다.
PreToolUse
도구 호출이 이루어지기 전에 실행되며, 도구 이름과 인자를 전달받습니다. hook은 승인, 거부, 혹은 재작성할 수 있습니다. 가장 효과적인 활용은 에이전트가 무인 상태에서 절대로 실행해서는 안 되는 명령군을 차단하는 것입니다—예: rm -rf, git reset --hard, git push --force 로 메인 브랜치에 푸시, gcloud auth revoke, 프로덕션에 대한 kubectl delete, “먼저 물어봐”를 “지금 바로 실행”으로 바꾸는 플래그 등.
hook 형태: 표준 입력(stdin)으로 도구 호출을 읽고, 위험한 조합을 패턴 매칭한 뒤, 비정상 종료 코드를 반환해 거부합니다. 실제 운영 중인 예시:
/tmp외의 경로에 대한rm -rf차단- 원격과 관계없이
main혹은master브랜치에git push --force차단 - 세션 환경 변수
ALLOW_NO_VERIFY=1이 명시되지 않은 한git commit --no-verify차단 --admin혹은--auto-merge플래그가 포함된 모든gh pr명령 차단
이 hook은 보안망이지 설정이 아닙니다. 에이전트는 원래 이런 행동을 하지 않도록 학습돼 있지만, 거의 실행 직전까지 가는 경우를 잡아줍니다.
PostToolUse
도구 호출이 완료된 뒤 실행되며, 도구 이름, 인자, 그리고 결과를 전달받습니다. hook은 원하는 부수 효과를 수행하고 종료합니다. 파일 쓰기 작업에 대해서는 여기서 lint, 포맷, 타입 체크 및 프로젝트 고유의 방어 로직을 실행합니다.
hook 형태: tool_name == "Write" 혹은 tool_name == "Edit" 인 경우에만 필터링하고, 기록된 파일 경로에 대해 해당 linter를 실행합니다. 제 설정에서는 다음과 같이 동작합니다.
- JavaScript/TypeScript →
prettier --write - Python →
ruff check --fix - Bash 스크립트 →
shellcheck
PostToolUse는 에이전트의 다음 행동을 막지 않습니다—이미 파일이 쓰여진 뒤에 실행되기 때문입니다. 가능한 부분은 조용히 고치고, 못 고치는 부분은 보고합니다.
이점: 일관성 확보. 에이전트가 만든 모든 파일은 제가 직접 작성하는 파일과 동일한 포맷을 갖게 됩니다. “빠르고 못생긴 코드”라는 에이전트의 출력이 의도적인 커밋과 구분되지 않을 정도가 됩니다.
Stop
에이전트가 응답을 마쳤을 때 실행됩니다. 대부분의 사용자가 이 hook을 건너뛰지만, 가장 큰 효과가 여기서 나타납니다.
제 Stop hook은 ~/.claude/hooks/session-end-commit.sh에 정의돼 있으며, 모든 Claude Code 세션이 끝날 때마다 git add -A && git commit -m "" 를 실행해 스냅샷을 남깁니다. 따라서 세션 동안 어떤 변화가 있었는지 언제든지 실제 커밋 기록을 통해 확인할 수 있습니다.
특히 stale‑lock 방어가 흥미롭습니다. 5월 15일에 세션‑end hook이 0바이트 .git/index.lock 파일 때문에 11일간 조용히 실패하고 있었다는 것을 발견했습니다. 이 파일은 5월 3일에 충돌한 git 프로세스가 남긴 것이었습니다. 해결책은 5줄짜리 블록으로, lock 파일이 존재하고 수정 시간(mtime)이 5분 이상이며, lsof 로 확인했을 때 어떤 프로세스도 잡고 있지 않을 경우에만 삭제하도록 했습니다.
- 살아있는 프로세스: 유지
- 오래된(lock) 파일: 삭제
건강한 자동 커밋은 1초 이내에 완료되므로, 5분 임계값은 실제 동시 실행과 충돌하지 않습니다.
교훈: hook은 점점 엣지 케이스를 축적합니다. 첫 번째 버전은 행복한 경로만 다루고, 1년간 매일 사용하면서 발견한 실패 모드를 처리하게 된 버전이 진짜 의미 있는 버전이 됩니다.
SessionStart
새로운 Claude Code 세션이 열릴 때 실행됩니다. 여기서 작업에 매번 필요한 컨텍스트를 미리 로드합니다. 목표는 “먼저 이 세 파일을 읽어라”라는 반복적인 프롬프트를 없애는 것입니다.
제가 사용하는 내용:
- 프로젝트의
SESSION-STATE.md– 진행 중인 일, 막힌 일, 다음 작업을 정리한 파일 - 현재 작업 디렉터리에 맞춰
~/nexus/agents/아래에 있는 관련 에이전트 컨텍스트 파일 - 어제 작업한 내용 요약 – 활성 프로젝트들의 지난 하루 커밋 로그
- 프로젝트에
queue파일이 있으면 활성 작업 목록
이 hook은 텍스트를 반환하고, 그 텍스트는 시스템 리마인더 형태로 세션에 주입됩니다. 즉, 첫 프롬프트를 입력하기 전부터 에이전트는 “어디까지 했는지, 지금 무엇을 하고 있는지, 다음에 해야 할 일은 무엇인지”를 이미 알고 있는 셈입니다.
이것이 Claude Code를 무상태 어시스턴트에서 지속적으로 함께 작업하는 협업 파트너로 바꾸는 핵심이며, 모델 자체를 바꾸지는 않습니다.
UserPromptSubmit
에이전트가 프롬프트를 보기 전에 실행됩니다. hook은 프롬프트를 재작성하거나, 컨텍스트를 추가하거나, 제출을 차단할 수 있습니다. 흔히 보이는 사례는 안전 단어 필터링처럼 지루한 경우지만, 흥미로운 경우는 프로젝트‑특화 방어입니다.
제가 사용하는 예시:
content-engine레포에서 프롬프트에 “ship”, “publish”, “deploy” 중 하나가 포함되면,--ship플래그 보호와 수동 배포 패턴을 알리는 메시지를 삽입- 프로덕션 디렉터리에서 단일 동사 명령(
run,start,deploy)이 들어오면, 더 안전한 대안을 설명하는CLAUDE.md섹션을 삽입 - 프롬프트에
~/nexus/agents/personal-contacts.md에 기록된 사람 이름이 등장하면, 해당 인물에 대한 맥락(예: 대학 동기, 현재 고용 관계, 이전 상호작용)을 삽입해 에이전트가 차가운 영업 메시지처럼 보지 않게 함
이 hook은 프롬프트를 차단하지 않고 보강합니다. 에이전트는 제가 직접 기억해 넣어야 할 컨텍스트가 이미 포함된 풍부한 프롬프트를 보게 됩니다.
전체적인 패턴
다섯 가지 hook 모두 같은 원리를 따릅니다.
“내가 직접 기억해서 적용하기엔 잊어버리기 쉬운 규칙을 hook에 코딩한다.”
hook은 반드시 매번 실행되며, 사용자가 기억해서 호출했는지와 무관하게 동작합니다.
이것이 2010년대에 자동 포맷‑온‑세이브가 팀 코딩 방식을 바꾼 이유와 동일합니다. 포맷터를 매번 수동으로 실행하는 기억에 의존하면, 어느 순간 스타일 가이드에서 벗어나게 됩니다.
AI 작업용 hook도 같은 레이어의 원시적인 도구입니다. 개별 운영자가 기관 차원에서 아직 제정되지 않은 규칙을 구현하는 방법이죠. 모든 엔지니어가 동일한 lint, 자동 커밋, 컨텍스트 로딩을 사용해 매 세션마다 일관된 작업 흐름을 유지한다면, 기업 차원의 도입 문서는 18개월 뒤에야 따라잡을 수준이 됩니다.
**여러분도 직접 만들어 보