
CLAUDE.md는 '부탁', Hook은 '강제'. Claude Code에게 규칙을 지키게 하는 방법
요약
Claude Code 사용 시 CLAUDE.md의 지시사항이 누락되는 문제를 해결하기 위해 Hook 기능을 활용하여 규칙을 강제하는 방법을 소개합니다. Git 상태를 확인하여 신규 및 기존 기사의 날짜 필드(pubDate, updatedDate)를 자동으로 검증하는 설계 방식을 다룹니다.
핵심 포인트
- CLAUDE.md는 지시서일 뿐 컨텍스트가 늘어나면 규칙 준수를 보장할 수 없음
- Hook 기능을 통해 특정 타이밍에 스크립트를 실행하여 규칙을 기계적으로 강제 가능
- Git의 추적 상태(tracked/untracked)를 활용해 신규 파일과 기존 파일의 규칙을 분리 설계
- exit code 2와 stderr를 활용하여 Claude에게 오류 내용을 전달하고 피드백 유도
전제: 개인 블로그를 Claude Code와 함께 운영하고 있습니다
Astro로 제작된 개인 블로그를 Claude Code의 도움을 받으며 운영하고 있습니다. 기사는 Markdown 형식이며, frontmatter(상단 메타 정보)에 pubDate(공개일)라는 필드를 가지고 있는데, 이것이 기사의 정렬 순서에도 영향을 미치는 중요한 값입니다.
CLAUDE.md만으로는 지켜지지 않는다
Claude Code를 사용할 때 CLAUDE.md에 운영 규칙을 작성하곤 합니다.
어느 날, 이런 일이 있었습니다.
Claude Code에게 "오늘은 며칠이야?"라고 물었더니, 직전에 공개한 기사의 pubDate로부터 역산한 잘못된 답변이 돌아왔습니다.
자신이 작성한 기사의 pubDate를 "오늘 날짜"라고 착각하여 잘못된 답변을 내놓은 것입니다. CLAUDE.md에는 "날짜는 시스템이 제공하는 currentDate를 최우선으로 참조한다"라고 제대로 적혀 있었음에도 말입니다.
Claude에게 이유를 물으니, 돌아온 답변은 "보장할 수 없다. CLAUDE.md는 어디까지나 지시서일 뿐이며, 읽기 누락이나 착각이 발생할 가능성은 남아 있다"라는 것이었습니다.
CLAUDE.md는 결국 "매번 읽어주길 바라는 부탁"에 불과하며, 컨텍스트(Context)가 늘어나면 뒷부분의 내용일수록 잊히기 쉽습니다. 문서에 쓰는 것만으로는 재발 방지가 되지 않는다는 것을 깨달았습니다.
그래서 Hook을 통해 기계적으로 강제하기로 했습니다.
Hook(훅)은 툴 실행 전후 등 특정 타이밍에 임의의 스크립트를 자동 실행할 수 있는 Claude Code의 기능입니다. settings.json에 설정을 작성하면 CLI 버전과 데스크톱 버전 모두 동일하게 작동합니다.
설계 포인트: 신규 기사와 기존 기사를 구분하기
처음에는 심플하게 "pubDate가 오늘 날짜와 일치하는가"만을 체크하려고 했습니다. 하지만 이렇게 하면, 며칠 전에 공개한 기사를 조금 수정했을 뿐인데 매번 걸린다는 허점이 있었습니다.
pubDate는 최초 공개일이므로 수정 시 변경 금지이며, 수정해야 할 것은 updatedDate(최종 수정일)입니다. 신규 기사와 기존 기사의 수정은 애초에 지켜야 할 규칙이 다릅니다.
그래서 Git의 관리 상태를 사용하여 판정을 나누었습니다.
function isTracked(relPath) {
try {
execSync(`git ls-files --error-unmatch "${relPath}"`, { stdio: 'ignore' });
...
git ls-files --error-unmatch는 해당 파일이 Git에 추적(Tracked)되고 있는지를 알려줍니다. 이를 통해 다음과 같이 분류합니다.
| 상태 | 판정 | 적용 규칙 |
|---|---|---|
| 미추적 (신규) | untracked | pubDate가 오늘 날짜가 아니면 차단 |
| 추적됨 (기존) | tracked | pubDate 변경 차단 / updatedDate가 오래되었으면 리마인드 |
기존 기사 측에서는 최근의 commit 내용과 비교하여 pubDate가 바뀌지 않았는지도 체크합니다.
function getHeadPubDate(relPath) {
try {
const headContent = execSync(`git show HEAD:"${relPath}"`, { encoding: 'utf8' });
...
왜 exit code 2로 멈추는가
Claude Code의 Hook에는 다음과 같은 규칙이 있습니다.
exit 0→ 성공, 아무 일도 일어나지 않음exit 2+stderr→stderr의 내용이 Claude에게 전달됨- 기타 → 인간에게만 표시되는 에러
PostToolUse의 경우, 툴 자체는 이미 실행된 상태이므로 exit 2는 "차단"이라기보다 "사후 리마인드"로서 기능합니다. 개인적으로는 stderr를 "Claude에게 전달하는 메시지 경로"로 사용할 수 있다는 발상이 흥미로운 포인트였습니다.
process.stderr.write(
`⚠️ 기존 기사의 pubDate를 변경하려고 합니다.\n` +
` pubDate는 최초 공개일이므로 변경하지 마세요.`
...
이 발상을 기사 톤 체크에도 응용했다
같은 논리로, "NG 표현(AI스러운 말투)을 사용하고 있지 않은가"를 체크하는 Hook도 만들었습니다. 〜わけです (〜라는 것입니다)
라든가 正直に言うと (솔직히 말하면)
같은 구절을 단순히 grep하여, 발견되면 리마인드해 주는 단순한 것입니다.
const NG_PATTERNS = [
{
pattern: /わけです/,
label: '〜わけです(説明口調)' // 〜わけです (설명조)
},
{
pattern: /正直に言うと/,
label: '正直に言うと'
},
...
규칙을 문서에 적어두는 것만으로는 Claude도 인간과 마찬가지로 "잊어버리거나" "읽지 못하는" 일이 발생합니다. 기계적으로 체크하는 메커니즘이 있어야 비로소 안심하고 운영을 돌릴 수 있다는 것이 이번의 배움이었습니다.
조금 더 자세한 경위는 블로그에 적어두었습니다
날짜를 착각했던 전말이나, 설계의 허점을 깨닫기까지의 흐름, 실제 테스트 방법 등은 운영 중인 블로그에 자세히 정리해 두었습니다.
Claude Code의 실전 Tips를 초보자용으로 여러 개 작성해 두었으니, 괜찮으시다면 그쪽도 확인해 보세요.
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기