Hooks — 행동 전후에 개입하는 23개의 문
이전 이야기에서: 지민은 CLAUDE.md로 Claude에게 프로젝트를 기억시켰다. 이제 "Next.js 14 프로젝트야"를 매번 말할 필요가 없었다. Claude가 더 자신감 있게 일하기 시작했다. 파일을 수정하고, 코드를 실행하고... 그러던 어느 날 밤.
Harness = Tools + Knowledge + Context + Permissions
Hooks는 Harness의 Permissions 제어 메커니즘이다. "Claude가 움직일 때 나도 움직이고 싶다"
그날 밤에 일어난 일
지민이 자고 있는 동안 Claude가 코드 리뷰를 하고 있었다.
테스트가 실패했다. Claude는 "수정하겠습니다"라며 코드를 고쳤다.
그리고 git push를 main 브랜치에 실행했다.
아침에 Slack 알림이 7개 와 있었다.
현우: @지민 프로덕션 빌드 실패함. 누가 main에 직접 push 했어?
현우: 새벽 2시에?
지민은 깨달았다. Claude에게 기억(CLAUDE.md)을 줬더니 자신감이 생겼다. 하지만 경계가 없었다.
CLAUDE.md로 Claude가 프로젝트를 기억하게 됐다. Claude는 이제 더 자신감 있게 행동했다. 파일을 고치고, 터미널 명령을 실행하고, 여러 파일을 한꺼번에 수정했다.
그런데 개발자들에게 불편함이 생겼다.
Claude가 파일을 수정함
→ 나는 수동으로 lint를 돌려야 함
→ 나는 수동으로 테스트를 실행해야 함
→ 나는 수동으로 포매터를 실행해야 함
→ ...
더 답답한 상황도 있었다.
Claude가 git push를 실행하려 함
→ 내가 원하지 않는 순간일 수 있음
→ 막을 방법이 없음
그리고 아주 사소하지만 삶의 질과 직결된 욕구도 있었다.
Claude가 30분짜리 작업을 하고 있음
→ 나는 딴 일을 하고 있음
→ 작업이 끝났는지 계속 탭을 확인해야 함
→ "끝나면 소리 내줘" 같은 게 있으면 좋겠다
"Claude의 모든 행동에 내가 개입할 수 있는 방법이 필요하다."
해결책의 탄생: "이벤트 시스템"
Anthropic은 Claude Code에 이벤트 기반 훅 시스템을 도입했다.
Agent Loop의 관점에서 보면, Hooks는 루프의 각 단계에 끼어드는 인터셉터다:
def agent_loop(messages):
while True:
response = client.messages.create(...)
for block in response.content:
if block.type == "tool_use":
# ← PreToolUse 훅 (허용/차단)
output = TOOL_HANDLERS[block.name](**block.input)
# ← PostToolUse 훅 (반응/주입)
핵심 아이디어는 단순하다: Claude가 특정 행동을 할 때마다, 지정한 스크립트를 실행한다.
{
"hooks": {
"PostToolUse": [{
"matcher": "Edit|Write",
"hooks": [{
"type": "command",
"command": "npm run lint"
}]
}]
}
}
Claude가 파일을 수정할 때마다(Edit|Write) → lint 자동 실행.
한 줄의 설정으로 수동 작업이 자동화됐다.
Harness에서 Hooks의 위치
Permissions 레이어가 없는 Harness:
Claude가 어떤 툴이든 아무 때나 실행 가능
→ rm -rf 실수, git push 실수, 프로덕션 DB 수정 실수
Permissions 레이어가 있는 Harness:
PreToolUse: 실행 전 차단/허용 결정
PostToolUse: 실행 후 반응 (린트, 테스트, 알림)
UserPromptSubmit: 입력에 컨텍스트 주입
→ Claude는 같은 루프를 돌지만, 행동 경계가 명확해진다
Permissions 레이어의 핵심 원칙:
"에이전트에게 필요한 권한만 준다. 최소 권한이 실수를 방지한다." Hooks는 이 원칙을 동적으로 실행한다.