Claude Code Hook으로 만드는 자기학습 시스템 — Knowledge & Gotchas 자동 수집
Claude Code Hook으로 만드는 자기학습 시스템
Knowledge & Gotchas 자동 수집
1. 문제 정의
Claude Code로 코드를 구현하다 보면 두 가지 경험이 반복된다.
- 같은 실수를 다시 한다 — 지난 세션에서 빌드 실패를 겪고 해결했는데, 다음 세션에서 똑같은 함정에 빠진다.
- 좋은 판단이 휘발된다 — "이 상황에선 A보다 B가 낫다"는 트레이드오프를 발견했지만, 다음 세션의 Claude는 이를 모른다.
CLAUDE.md에 모든 것을 적을 수는 있지만, 프로젝트 전체에 적용되는 보편 규칙과 특정 작업 맥락에서만 유효한 판단은 성격이 다르다. 전자가 비대해지면 참조 비용이 높아지고, 후자가 누락되면 같은 실수가 반복된다.
2. 해결: 두 가지 자동 수집 시스템
세션 종료 시점에 대화 내역을 자동 분석하여, 유의미한 경험을 사용자 확인을 거쳐 축적하는 Hook 기반 피드백 루프를 구축한다.
| 시스템 | 성격 | 질문 | 수집 대상 |
|---|---|---|---|
| Knowledge | 능동적 | "어떤 선택이 더 좋았는가?" | 트레이드오프, 환경 노하우, 스케일 전환점 |
| Gotchas | 방어적 | "어떤 함정을 피해야 하는가?" | 실패 패턴, 반복 오류, 환경 함정 |
비유로 이해하기
CLAUDE.md→ 법률 (프로젝트 전체 보편 규칙)Knowledge→ 판례 (상황별 참고 선례)Gotchas→ 사고 보고서 (재발 방지 매뉴얼)
둘은 동등한 레벨의 컨텍스트 파일이다. 같은 디렉토리에, 같은 규칙으로, 같은 생명주기를 따른다.
3. 핵심 메커니즘: exit code 계약
Claude Code의 Hook은 특정 이벤트 시점에 쉘 커맨드를 실행한다. Hook 스크립트는 stdin으로 대화 트랜스크립트(JSON)를 받으며, exit code가 후속 동작을 결정한다.
| exit code | 동작 |
|---|---|
0 | 무동작 — Hook 조용히 종료 |
2 | stdout을 Claude에게 주입 — 대화가 이 컨텍스트와 함께 계속됨 |
이것이 전체 시스템의 핵심이다. Hook이 직접 파일을 수정하는 게 아니라,
exit 2로 Claude에게 "이런 상황이 감지됐으니 사용자에게 기록 여부를 확인하라"는 프롬프트를 주입한다. 자동 기록은 노이즈를 만들지만, 사람의 판단이 개입하면 품질이 유지된다.
4. Knowledge — 맥락적 판단 기록
4.1 수집 대상
| 유형 | 예시 |
|---|---|
| 트레이드오프 판단 | "이 규모에선 Server Action보다 API Route가 캐싱에 유리했다" |
| 환경 특화 노하우 | "WSL 환경에서 Playwright는 headless 모드 강제가 필요했다" |
| 스케일 전환점 | "컴포넌트 50개 이상부터 barrel export가 빌드 시간을 2배로 늘렸다" |
4.2 동작 조건
수집 스크립트는 트랜스크립트를 파싱하여 코드 구현 작업이 수행된 세션인지 판별한다. 단순 질의응답이나 리서치 세션에서는 동작하지 않는다.
4.3 저장 위치 및 형식
각 Skill 디렉토리의 독립 파일로 저장된다: ~/.claude/skills/{skill-name}/knowledge.md
### [2026-04-10] 제목
- **상황**: 어떤 맥락이었는지
- **판단**: 무엇을 선택했는지
- **이유**: 왜 그렇게 판단했는지
- **결과**: 어떤 결과가 나왔는지
4.4 생명주기
Knowledge는 무한히 쌓이면 참조 비용이 높아진다. 밀도를 유지하기 위해 생명주기를 관리한다.
| 단계 | 조건 | 동작 |
|---|---|---|
| 교체 | 같은 주제 + 더 나은 판단 | 기존 항목 대체 |
| 승격 | 2곳 이상에서 반복 검증 | CLAUDE.md로 이동, knowledge에서 제거 |
| 폐기 | 기술 스택 변경으로 무효화 | 제거 |
| 상한 | 최대 10건 | 초과 시 승격 또는 폐기 우선 |
5. Gotchas — 실패 패턴 방어막
5.1 핵심 설계: 성공 시 무시
수집 스크립트는 대화에서 성공 완료 마커를 감지하면 즉시 exit 0으로 종료한다. 실패가 발생한 세션에서만 동작한다.
5.2 감지 → 필터 → 제안 흐름
| 단계 | 동작 | 탈락 조건 |
|---|---|---|
| 1. 성공 확인 | 완료 마커 존재 여부 | 성공 완료 → exit 0 |
| 2. 작업 감지 | 어떤 작업이 수행됐는지 | 작업 미감지 → exit 0 |
| 3. 실패 매칭 | 정규식으로 실패 패턴 탐색 | 실패 없음 → exit 0 |
| 4. 중복 체크 | 이미 기록된 실패인지 확인 | 이미 기록됨 → exit 0 |
| 5. 제안 | exit 2로 Claude에게 전달 | — |
4단계까지 모두 통과한 새로운 실패만 Claude에게 전달된다.
5.3 저장 위치 및 형식
저장 위치: ~/.claude/skills/{skill-name}/gotchas.md
- **{실패 상황 제목}**
- What happens: 어떤 증상이 발생하는지
- How to fix: 발생 시 해결 방법
- Prevention: 재발 방지를 위해 흐름에 추가할 체크
Gotchas도 Knowledge와 동일한 생명주기(교체/승격/폐기/상한 10건)를 따른다. Claude가 다음 구현 시 이 파일을 읽으면서 자연스럽게 같은 함정을 회피한다.
6. 세팅 방법
6.1 디렉토리 구조
~/.claude/
├── settings.json # Hook 등록
├── CLAUDE.md # 자동 로드 규칙 + 생명주기 규칙
├── hooks/
│ ├── knowledge-collector.js # Knowledge 수집 (Stop Hook)
│ └── gotchas-collector.js # Gotchas 수집 (Stop Hook)
└── skills/
└── {skill-name}/
├── SKILL.md # Skill 지시사항 (자동 로드)
├── gotchas.md # 실패 방지 가이드 (있으면 자동 참조)
└── knowledge.md # 맥락적 판단 선례 (있으면 자동 참조)
Knowledge와 Gotchas는 동등한 구조를 가진다. 둘 다 Skill 디렉토리에 독립 파일로 존재하고, 파일이 있으면 자동으로 참조된다.
6.2 CLAUDE.md에 글로벌 자동 로드 규칙 추가
각 SKILL.md에 "knowledge.md를 읽어라"고 쓰는 대신, CLAUDE.md에 글로벌 규칙 하나로 모든 Skill에 일괄 적용한다:
## Skill 컨텍스트 시스템
Skill 실행 시 해당 Skill 디렉토리에 아래 파일이 존재하면 **실행 전 반드시 읽고 참고**한다:
- `gotchas.md` — 실패 방지 가이드 (What happens / How to fix / Prevention)
- `knowledge.md` — 맥락적 판단 선례
이 방식의 장점:
- 새 Skill을 추가할 때 SKILL.md에 참조 지시를 넣을 필요가 없다
- gotchas.md나 knowledge.md 파일을 놓기만 하면 자동으로 적용된다
- SKILL.md는 지시사항에만 집중하여 가벼워진다
6.3 수집 스크립트 작성
두 스크립트 모두 동일한 구조를 따른다.
| 순서 | 처리 | 설명 |
|---|---|---|
| 1 | stdin 파싱 | { transcript: [...] } 형태의 JSON 수신 |
| 2 | 텍스트 추출 | 각 메시지에서 텍스트 블록 추출 |
| 3 | 작업/실패 감지 | 정규식 패턴으로 조건 판별 |
| 4 | 조건 분기 | 해당 없으면 exit 0, 해당 시 exit 2 |
| 5 | stdout 출력 | Claude에게 주입할 프롬프트 (기록 형식, 규칙 안내) |
Knowledge 수집기는 "구현 작업이 수행됐는가?"를 판별하고, Gotchas 수집기는 "실패 패턴이 있는가? + 이미 기록된 실패인가?"를 판별한다.
Gotchas 수집기의 중복 체크는 gotchas.md 파일을 직접 읽어서 확인한다:
function isAlreadyInGotchas(skillName, failureType) {
const gotchasFile = path.join(HOME, ".claude", "skills", skillName, "gotchas.md");
try {
const content = fs.readFileSync(gotchasFile, "utf-8");
// 실패 타입별 키워드로 중복 체크
return keywords.some((kw) => new RegExp(kw, "i").test(content));
} catch {
return false; // 파일 없으면 중복 아님
}
}
6.4 settings.json에 Hook 등록
Stop 이벤트에 두 스크립트를 command 타입으로 등록한다.
{
"hooks": {
"Stop": [
{
"hooks": [
{ "type": "command", "command": "node $HOME/.claude/hooks/gotchas-collector.js" },
{ "type": "command", "command": "node $HOME/.claude/hooks/knowledge-collector.js" }
]
}
]
}
}
7. 동작 흐름 요약
코드 구현 세션 진행
│
▼
Claude 응답 완료 ── Stop 이벤트 발생
│
┌─────┴─────┐
▼ ▼
Knowledge Gotchas
수집기 수집기
│ │
│ ├─ 성공 완료? → exit 0
│ ├─ 실패 없음? → exit 0
│ ├─ 이미 기록? → exit 0
│ └─ 새 실패 → exit 2 (제안)
│
├─ 구현 작업 아님? → exit 0
└─ 구현 작업 → exit 2 (평가 요청)
│
▼
Claude가 사용자에게 기록 여부 확인
│
▼
승인 시 해당 Skill의 gotchas.md 또는 knowledge.md 업데이트
8. 과부하 방지: 자동 건강 관리
수집만 있고 정리가 없으면 컨텍스트가 비대해진다. 이 문제를 해결하기 위해 UserPromptSubmit Hook으로 세션 시작 시 자동 건강 체크를 수행한다.
8.1 측정 대상
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{ "type": "command", "command": "node $HOME/.claude/hooks/memory-health-check.js" }
]
}
]
}
}
건강 체크 스크립트는 세션당 1회만 실행되며(session_id 기반 중복 방지), 4개 시스템을 측정하여 [HEALTH] 형식으로 Claude에게 주입한다:
[HEALTH] MEMORY.md: 130/150줄 | memory 파일 6개 | stale(30d+): 2건
[HEALTH] CLAUDE.md: 191/120줄 ⚠️
[HEALTH] Gotchas: sh-dev-loop(5건) ship(4건) unit-test(6건) | 총 26건
[HEALTH] Knowledge: 없음
8.2 CLAUDE.md에 대응 규칙 추가
Claude는 [HEALTH] 데이터를 받으면 CLAUDE.md의 규칙에 따라 행동한다:
| 상태 | 행동 | 자동/수동 |
|---|---|---|
| 정상 | 무시, 사용자 작업에 집중 | — |
| Memory stale(30d+) | 현재 코드 대조 → 무효 시 제거/갱신 + 한 줄 보고 | 자동 |
| Gotchas/Knowledge 8건+ | 무효/중복 항목 제거 또는 승격 처리 + 한 줄 보고 | 자동 |
| CLAUDE.md 줄 수 초과 | "CLAUDE.md가 N줄입니다. 정리할까요?" | 사용자 확인 |
수집과 정리가 모두 자동화되어, 사용자가 직접 관리 파일을 신경 쓸 필요가 없다.
9. 다른 접근과의 비교
9.1 왜 CLAUDE.md에 직접 쓰지 않는가?
| 기준 | CLAUDE.md 직접 기록 | Knowledge + Gotchas |
|---|---|---|
| 적용 범위 | 프로젝트 전체 | 특정 Skill 맥락 |
| 비대화 위험 | 높음 — 모든 경험이 누적 | 낮음 — 10건 상한 + 생명주기 |
| 참조 비용 | 항상 전체 로드 | 해당 Skill 실행 시에만 로드 |
| 승격 경로 | — | 반복 검증 시 CLAUDE.md로 이동 |
9.2 왜 Hook이 직접 쓰지 않는가?
| 기준 | Hook 자동 기록 | exit 2 제안 방식 |
|---|---|---|
| 품질 관리 | 노이즈 누적 | 사용자 확인 후 기록 |
| 오탐 처리 | 삭제 필요 | 거부하면 기록 안 됨 |
| 맥락 이해 | 정규식 수준 | Claude가 맥락을 해석 |
9.3 왜 SKILL.md 안에 넣지 않는가?
기존에는 Gotchas를 SKILL.md 내부 ## Gotchas 섹션으로, Knowledge를 별도 파일로 관리했다. 이 비대칭 구조에는 문제가 있었다:
| 기준 | SKILL.md 내부 섹션 | 독립 파일 (gotchas.md) |
|---|---|---|
| SKILL.md 크기 | Gotchas 포함으로 비대 | 지시사항만, 가벼움 |
| 새 Skill 추가 | 섹션 수동 추가 필요 | 파일만 놓으면 자동 적용 |
| 구조 일관성 | Knowledge와 다른 구조 | 둘 다 동일한 독립 파일 |
| 관리 규칙 | 각각 다른 규칙 | 동일한 생명주기 |
Knowledge와 Gotchas는 동등한 레벨의 컨텍스트이므로, 동일한 구조(독립 파일)와 동일한 규칙(생명주기)으로 관리하는 것이 자연스럽다.
10. 활용할 수 있는 다른 Hook 이벤트
Knowledge/Gotchas는 Stop 이벤트를, 건강 관리는 UserPromptSubmit 이벤트를 사용하지만, 같은 패턴으로 다른 이벤트에도 자동화를 연결할 수 있다.
| 이벤트 | 시점 | 활용 예시 |
|---|---|---|
UserPromptSubmit | 사용자 프롬프트 제출 | 컨텍스트 건강 체크 (세션당 1회) |
Stop | Claude 응답 완료 | Knowledge/Gotchas 수집 |
TaskCompleted | Task 완료 | 자동 검증 에이전트 실행 |
TeammateIdle | 팀원 에이전트 유휴 | 타입 체크, 린트 검증 |
TaskCompleted에는 agent 타입 Hook을 연결하여 검증 에이전트를 자동 실행하는 것도 가능하다.
11. 참고 자료
| 자료 | 링크 |
|---|---|
| Claude Code Hooks 공식 문서 | code.claude.com/docs/en/hooks.md |
| Claude Code Settings 공식 문서 | code.claude.com/docs/en/settings.md |
| Claude Code Skills 공식 문서 | code.claude.com/docs/en/skills.md |
| Claude Code Memory & CLAUDE.md 공식 문서 | code.claude.com/docs/en/memory.md |