Your CLAUDE.md에 '항상 테스트 실행'이라고 적혀 있다면 — 실제로 의미하는 바는 hooks입니다
요약
기존의 규칙 파일(.md) 기반 에이전트 지침은 모델의 주의력 경쟁에서 패배하기 쉽습니다. Claude Code는 PreToolUse, PostToolUse 등 '훅(hooks)'이라는 메커니즘을 제공하여, 에이전트 생명주기의 특정 시점에 실행되는 셸 명령으로 강제 제어를 가능하게 합니다. 이는 단순한 상기(reminder)가 아닌 게이트 역할을 수행합니다.
핵심 포인트
- 규칙 파일(.md)은 권고성 컨텍스트이며 신뢰도가 낮습니다.
- Claude Code의 '훅'은 에이전트 생명주기의 특정 시점에 실행되는 셸 명령어입니다.
- PreToolUse 훅은 도구 호출 전에 작동하여 가장 강력한 강제 제어 로직을 구현할 수 있습니다.
- 종료 코드(Exit Code)와 stderr 메시지를 활용해 모델에게 실패 원인을 명확히 피드백할 수 있습니다.
대부분의 에이전트 설정에는 범주 오류(category error)가 숨어 있습니다. 바로 규칙 파일(rules file)을 강제 메커니즘(enforcement mechanism)으로 취급하는 것입니다. 하지만 그것은 그렇지 않습니다. CLAUDE.md, AGENTS.md, .cursor/rules 등은 권고성 컨텍스트입니다. 모델은 이를 읽고, 자신의 윈도우 내의 모든 다른 정보와 비교하여 가중치를 부여하며, 보통은 준수합니다. '보통'은요.
실패 양상은 극적이지 않습니다. 그저 40턴(turn) 세션에서 'main 브랜치에 직접 커밋하지 마라'는 지침이 현재 작업 내용보다 30,000 토큰이나 뒤처져 있고, 모델이 — 도움을 주려 하면서 — main에 직접 커밋하는 식입니다. 아무도 당신의 말을 거역한 것이 아닙니다. 단지 당신의 상기(reminder)가 주의력 경쟁(attention contest)에서 패배했을 뿐입니다.
Claude Code에는 이러한 규칙들이 그 경쟁에서 지면 안 되는 메커니즘, 즉 hooks가 있습니다. 훅(hook)은 에이전트 생명 주기(lifecycle)의 특정 시점에 실행되는 셸 명령어(shell command)입니다. 이는 모델에게 무언가를 기억하라고 요청하는 것이 아니기 때문에, 아예 모델 내부에서 실행되지 않습니다.
본 포스트에서는 중요한 메커니즘들, 제 역할을 다하는 다섯 가지 훅(hooks), 그리고 제가 이들을 구현하면서 부딪혔던 함정들에 대해 다룹니다.
정신적 모델: 상기 vs. 게이트
CLAUDE.md의 규칙은 상기(reminder)입니다. 반면, 훅(hook)은 게이트(gate)입니다.
-
상기의 신뢰성은 컨텍스트 길이와 함께 _감쇠(decays)_합니다. 게이트의 신뢰성은 일정하며 — 그저 프로세스 종료 코드(process exit code)일 뿐입니다.
-
상기는 모델이 무엇을 _의도하는지(intends)_를 형성합니다. 게이트는 모델이 무엇을 _행할 수 있는지(does)_를 제한합니다.
-
당신은 둘 다 필요합니다: 판단 호출(
-
PreToolUse는 도구 호출이 실행되기 전에 작동합니다. 여기서 발생할 도구 호출을 차단할 수 있습니다.
-
PostToolUse는 도구 호출이 완료된 후에 작동합니다. 이 행동은 되돌릴 수 없지만, 이에 대해 보고하고 그 결과를 Claude에게 다시 전달할 수는 있습니다.
-
UserPromptSubmit, SessionStart, Stop 등 다른 이벤트들은 나머지 생명주기(lifecycle)를 담당합니다. Stop과 UserPromptSubmit도 차단할 수 있습니다("테스트가 통과할 때까지 멈추지 마세요"는 실제로 사용되는 패턴입니다). 하지만 가장 많은 강제 제어(enforcement) 로직이 존재하는 곳은 PreToolUse입니다.
작동하는 데 필요한 세 가지 기술적 세부 사항이 대부분의 작업을 처리합니다:
- 사용자의 훅(hook)은 stdin으로 JSON을 받습니다. Bash에 대한 PreToolUse 훅의 경우, 해당 JSON에는 실행될 정확한 명령어가 포함됩니다. 사용자가 이를 파싱하고(jq를 사용해도 됩니다), 결정한 후 종료합니다.
- 종료 코드 2는 호출을 차단하며 — 사용자의 stderr이 모델에게 피드백으로 전달됩니다. 이것이 훅을 외부 CI 검사보다 더 좋게 만드는 세부 사항입니다: 에이전트는 단순히 실패하는 것이 아니라, 왜 실패했는지 읽어냅니다.
Blocked: direct commit to main. Create a branch first.와 같은 stderr 메시지는 바로 다음 행동을 수정합니다(course-corrects). 종료 코드 0은 호출이 정상적인 권한 흐름을 통해 진행되도록 합니다. - 더 세밀한 제어를 위해, stdout으로 JSON을 내보냅니다. 이 구조화된 출력은 명시적으로 허용(allow), 거부(deny)하거나 "인간에게 문의(ask the human)" 결정으로 에스컬레이션할 수 있으며, 그 이유를 담고 있습니다. 단순 종료 코드는 90%의 경우를 처리하지만, 세 번째 옵션이 필요할 때는 JSON을 사용해야 합니다.
내재화할 가치가 있는 또 다른 속성이 있습니다: 훅은 모델이 매개하는 안전 계층(safety layer) 밖에, 사용자의 권한으로 실행됩니다. 이는 양날의 검입니다 — 그래서 신뢰할 수 있지만, 설치하기 전에 작성하지 않은 모든 훅을 읽어봐야 하는 이유이기도 합니다.
제 역할을 다하는 다섯 가지 훅 (Five hooks that earn their keep)
이들은 의도적으로 작습니다. 훅은 한 화면에서 읽을 수 있어야 합니다 — 이는 보안 제어(security control)이며, 읽기 어려운 보안 제어는 검토되지 않습니다.
1. main 브랜치 가드 (Bash의 PreToolUse). 들어오는 명령을 파싱합니다. 만약 해당 명령이 git commit이고 git branch --show-current가 main 또는 master를 나타낸다면,
과도한 차단(Overblocking)은 우회 경로를 가르칩니다. 만약 게이트가 너무 공격적이라면, 모델은 매처(matcher)를 회피하는 명령을 구성하기 시작합니다. 이는 악의적인 것이 아니라, 단지 진행에 최적화하려는 과정일 뿐입니다. 블록이 발동할 때마다 stderr 메시지는 항상 합법적인 다음 단계를 명시해야 합니다. 그래야 저항이 가장 적은 경로가 여러분의 규칙 내부에 머무를 수 있습니다.
완화하기 전에 로그를 확인하세요. 약화시키기 전에 로깅 전용 모드(log-only mode)로 새 후크를 실행하거나 (또는 단순히 블록을 검토하여) 일주일 동안 테스트해 보세요. 제 로그에 따르면,
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기