15. Hooks(훅)
🎯 이 장의 목표
- Hook이 무엇이고 왜 "모델 지침"보다 강력한지 이해한다
- 주요 라이프사이클 이벤트와 그 쓰임을 안다
- PreToolUse 훅으로 위험한 동작을 차단하는 법을 안다
- 훅이 "보안 경계"가 아니라 "기회적 가드레일"임을 이해한다
Hook이란
Hook은 Claude Code에서 무언가 일어날 때 실행되는 이벤트 기반 스크립트입니다. 프롬프트(모델의 해석에 의존)와 달리, 훅은 결정적 코드를 실행합니다 — 환각하지 않습니다.
비유하자면, 훅은 생산 라인의 자동 센서입니다. 작업자(모델)가 깜빡하든 말든, 특정 지점을 지나는 모든 제품에 대해 정해진 검사가 기계적으로 발동합니다.
📌 핵심 대비: CLAUDE.md에 "절대 rm -rf 쓰지 마"라고 적으면 컨텍스트 압박에 잊히거나 무시될 수 있습니다(9장에서 본 "안내일 뿐"). 하지만 PreToolUse 훅으로 rm -rf를 막으면 매번, 결정 시점에, 에러 메시지와 함께 발동합니다.
왜 훅이 필요한가
훅 없이는 모든 안전장치가 "모델이 지침을 이해하는지"에 달려 있습니다. 훅이 있으면 시스템 수준에서 규칙을 강제합니다:
rm -rf를 실행 전에 차단- 프로젝트 컨텍스트 자동 주입
- 모든 도구 호출을 감사 로그로 기록
- 편집 후 자동 린트 실행
flowchart LR
A[이벤트 발생<br/>예: 도구 실행 직전]:::user --> B[훅 스크립트 실행<br/>결정적 코드]:::tool
B --> C{종료 코드}:::danger
C -->|0: 허용| D[도구 실행]:::result
C -->|2: 거부| E[차단 + 에러 메시지]:::danger
C -->|1: 오류| F[비차단 오류]:::agent
classDef user fill:#FFE082,stroke:#F9A825,color:#000
classDef tool fill:#90CAF9,stroke:#1E88E5,color:#000
classDef danger fill:#EF9A9A,stroke:#E53935,color:#000
classDef result fill:#A5D6A7,stroke:#43A047,color:#000
classDef agent fill:#80DEEA,stroke:#00ACC1,color:#000
📌 핵심
예를 들어 PreToolUse 훅은 도구 실행 전 발동해, 도구 이름·입력을 stdin으로 받습니다. 종료 코드 2면 호출을 거부하고, 0이면 허용합니다.주요 라이프사이클 이벤트
훅이 발동하는 지점은 30개가 넘습니다. 실제로 자주 쓰는 것들:
| 이벤트 | 발동 시점 | 차단 가능? | 용도 |
|---|---|---|---|
| UserPromptSubmit | 프롬프트 제출 시 | ✅ | 프롬프트 검사·수정 |
| PreToolUse | 도구 실행 직전 | ✅ | 1차 보안 체크포인트 |
| PermissionRequest | 권한 요청 시 | ✅ | 자동 승인/거부 |
| PostToolUse | 도구 완료 후 | ❌(로깅만) | 린트 실행·결과 기록 |
| Stop / SubagentStop | Claude/서브에이전트 종료 시 | ✅ | 미완 작업 계속 강제 |
| PreCompact | 컨텍스트 컴팩션 직전 | ✅ | 트랜스크립트 백업 |
| SessionStart / SessionEnd | 세션 시작/끝 | ❌ | 컨텍스트 로드·정리 |
| InstructionsLoaded | 지침 파일 로드 시 | — | 어떤 규칙이 로드됐는지 디버깅 |
💡 팁
정보성 이벤트(차단 불가)는 막을 순 없지만 로깅·알림에 좋습니다. 예: PostToolUse로 편집 후 린트 결과를 주입, SessionEnd로 정리 작업.훅 작성과 범위
훅은 settings.json의 hooks에 정의하며, 정의 위치가 곧 범위를 정합니다.
| 위치 | 범위 | 용도 |
|---|---|---|
프로젝트 .claude/settings.json | 팀 공유 | 팀 정책 (커밋) |
개인 ~/.claude/settings.json | 모든 프로젝트 | 개인 가드레일 |
활용 예:
- 비-협상 규칙:
.env파일 절대 읽기 금지, 외부 서비스에 글 올리기 전 항상 확인, 모든 도구 호출 감사 로깅 - 계속 유도: Stop 훅이 Claude의 최종 응답을 검토해, 미완 작업을 "다 됐다"고 합리화하면 계속하도록 강제
- 결정 시점 컨텍스트 주입: post-write 린트 결과, pre-tool 보안 경고
💡 팁
프롬프트 기반 훅은 기본 30초 타임아웃입니다. 필요하면 timeout 필드로 조정하세요. (Haiku 등이 JSON을 코드펜스로 감싸 파싱이 깨지지 않도록, 출력 형식을 명확히 지시하는 게 좋습니다.)⚠️ 훅은 "보안 경계"가 아니다
매우 중요한 구분입니다. 훅은 결정적이지만 보안 경계는 아닙니다. 프롬프트 주입으로 우회될 수 있습니다.
flowchart TB
A[Hook]:::tool --> B{무엇인가}:::agent
B --> C[기회적 시점의<br/>'구조화된 개입']:::result
B -.아님.-> D[난공불락의 벽]:::danger
classDef tool fill:#90CAF9,stroke:#1E88E5,color:#000
classDef agent fill:#80DEEA,stroke:#00ACC1,color:#000
classDef result fill:#A5D6A7,stroke:#43A047,color:#000
classDef danger fill:#EF9A9A,stroke:#E53935,color:#000
훅은 가드레일이지 벽이 아닙니다(guardrails, not walls). 도구 호출을 가로채고, 컨텍스트를 주입하고, 알려진 나쁜 패턴을 막고, 에이전트 행동을 유도하는 — 기회가 좋은 시점의 구조화된 개입입니다. 진짜 격리가 필요하면 OS 수준 샌드박싱을 써야 합니다(→ 23장).
📌 핵심
정리하면: 강제력은 deny 규칙 > 훅 > CLAUDE.md 순이고, OS 수준 격리가 필요하면 샌드박스. 용도에 맞는 층을 고르세요.메모리·스킬·서브에이전트와 비교
자주 헷갈리는 네 가지를 정리하면:
| 메커니즘 | 본질 | 발동 |
|---|---|---|
| CLAUDE.md / Skill | 지식(무엇을 알/할지) | 모델이 판단 |
| Subagent | 격리된 일꾼 | 위임 시 |
| Hook | 결정적 코드 | 라이프사이클 이벤트에 자동 |
💡 팁
"내 에이전트 설정이 엉망"인 상황의 흔한 원인은 층을 헷갈리는 것입니다. 강제해야 할 행동 제약을 시스템 프롬프트(CLAUDE.md)에 적거나, 라우팅 로직을 서브에이전트 안에 넣는 식이죠. 강제·이벤트 기반은 훅, 지식은 CLAUDE.md/스킬, 격리는 서브에이전트입니다.이 장에서 배운 것
- Hook은 라이프사이클 이벤트에 발동하는 결정적 스크립트로, 모델 지침과 달리 환각하지 않는다.
- 주요 이벤트: PreToolUse(1차 보안 체크포인트)·PostToolUse·Stop·PreCompact·SessionStart 등. PreToolUse는 종료 코드로 허용(0)/거부(2)를 결정한다.
settings.json의hooks에 정의하며 위치가 범위를 정한다(프로젝트=팀, 유저=개인).- 훅은 가드레일이지 벽이 아니다 — 프롬프트 주입으로 우회 가능. 진짜 격리는 샌드박스.
- 강제력은 deny 규칙 > 훅 > CLAUDE.md. 층을 헷갈리지 말 것.
✍️ 확인 문제
- "절대
rm -rf금지"를 CLAUDE.md에 적는 것과 PreToolUse 훅으로 막는 것은 어떻게 다른가요? - PreToolUse 훅에서 종료 코드 0과 2는 각각 무엇을 의미하나요?
- "훅은 가드레일이지 벽이 아니다"라는 말의 의미와, 진짜 격리가 필요할 때의 대안은 무엇인가요?
다음 장: 16. Plugins(플러그인) — 지금까지의 확장들을 묶어 배포하기.