본문으로 건너뛰기

© 2026 Molayo

Zenn헤드라인2026. 06. 22. 09:16

Claude Code 하네스에 「자기 수복 루프 (Self-healing Loop)」를 구현한 이야기

요약

Claude Code의 피드백이 다음 세션에 반영되지 않는 문제를 해결하기 위해 3개의 훅(Hook)을 활용한 '자기 수복 루프' 구현 방법을 소개합니다. feedback.md의 내용을 시스템 컨텍스트에 자동으로 주입하여 Claude가 이전 실수를 반복하지 않도록 개선합니다.

핵심 포인트

  • feedback.md가 컨텍스트에 자동으로 포함되지 않는 문제 해결
  • UserPromptSubmit 훅을 통한 최근 피드백 자동 주입 메커니즘
  • 토큰 절약을 위해 피드백의 핵심 요약만 추출하여 주입
  • 기록-적용-감사-재기록으로 이어지는 닫힌 루프 구축

Claude Code 하네스에 「자기 수복 루프 (Self-healing Loop)」를 구현한 이야기

TL;DR

Claude Code의 피드백을 파일에 기록하더라도, 다음 세션에서 Claude가 그것을 읽지 않는다면 의미가 없다. 3개의 훅 (Hook)을 추가하여 「기록 → 적용 → 감사 → 다시 기록」의 닫힌 루프를 만든 이야기다.

이 기사의 전제 지식

이 기사는 Claude Code의 훅 (Hook) 시스템을 사용하여, 하네스 (Harness)를 자기 개선하는 메커니즘을 구현한다. 다음 기사들을 먼저 읽어두면 이해하기 쉽다.

Claude Code Hooks로 「세션 종료 시 자동 요약」을 만든 이야기— Stop hook의 기본 구성. 본 기사에서 확장할 Stop 훅의 원래 설계 -
Claude Code가 세션을 넘어 「기억하는」 메커니즘을 직접 만들었다— feedback.md 등의 메모리 파일 설계. 본 기사의 주입 대상 -
Windows에서 Claude Code 훅을 작성할 때 빠지기 쉬운 함정— Windows 환경에서의 bash 훅 구현 시 주의점

「기록하지만 적용하지 않는」 anti-pattern

하네스를 키운 지 얼마 지나지 않아, 다음과 같은 운용이 된다.

  • Claude가 실수한다
  • feedback.md에 「다음부터는 이렇게 해줘」라고 적는다
  • 다음 세션에서 똑같은 실수를 한다
  • feedback.md에 또 추가한다

기록은 늘어나지만 개선되지 않는다. feedback.md가 「아무도 읽지 않는 회의록」이 되어버린 상태다.

원인은 단순하다. feedback.md는 Claude Code가 자동으로 읽는 파일이 아니다. CLAUDE.md와 달리, 명시적으로 주입하지 않으면 컨텍스트 (Context)에 들어가지 않는다.

해결 아키텍처: 3개의 훅으로 닫는 루프

추가한 3개의 훅은 각각 역할이 다르다.

훅 (Hook)타이밍역할
UserPromptSubmit프롬프트 전송 전최근 피드백을 자동 주입
...

훅 1: feedback.md 자동 주입

문제의 구조

feedback.md는 수동으로 작성하는 파일이다. 세션을 넘어 유지되지만, Claude가 능동적으로 읽으러 오지는 않는다.

해결: UserPromptSubmit 훅으로 주입한다

프롬프트를 전송할 때마다, feedback.md의 마지막 3개 엔트리의 제목과 적용 방법(How to apply)만을 시스템 컨텍스트 (System Context)에 삽입한다.

# ~/.claude/hooks/harness_inject.sh (발췌)
FEEDBACK_FILE="$HOME/.claude/memory/feedback.md"
if [[ -f "$FEEDBACK_FILE" ]]; then
...

settings.json에 추가:

{
"hooks": {
"UserPromptSubmit": [
...

주입되는 내용의 이미지

--- 최근 피드백 (자동 주입) ---
**[2026-05-18] Windows 경로를 그대로 기사에 작성함**
How to apply: 기사의 경로는 ~/.claude/ 형식으로 통일할 것
...
```mermaid 블록으로 감싸기
----------------------------------------------

전체가 아니라 「제목」과 「How to apply」 2줄만 추출하는 이유는 토큰 (Token) 절약 때문이다. 피드백이 20건 이상이 되어도 컨텍스트 (Context)의 팽창을 방지할 수 있다.

...

#!/usr/bin/env bash

~/.claude/hooks/content_quality_guard.sh

FILE="$CLAUDE_TOOL_INPUT_PATH"

대상은 articles/ 디렉토리 하위의 .md 파일만

if [[ "$FILE" != "/articles/" ]] || [[ "$FILE" != *.md ]]; then
exit 0
fi

코드 펜스 (Code Fence)를 제외한 본문에서 위반 패턴을 검출

python3 - "$FILE" <<'EOF'
import sys, re
path = sys.argv[1]
with open(path, encoding="utf-8") as f:
text = f.read()

코드 펜스 (Code Fence) 블록을 제거

text_no_code = re.sub(r'.*?', '', text, flags=re.DOTALL)
disclaimer = r'시산|이론치|가정|추정|전망|개산|상정'
patterns = [
r'\d+\s*%\s*(삭감|개선|향상|단축)',
r'\d+\s배\s(빠르게|효율|개선)',
r'\d+\s주간\s측정',
]
violations = []
for pat in patterns:
for m in re.finditer(pat, text_no_code):
context = text_no_code[max(0, m.start()-30):m.end()+30]
if not re.search(disclaimer, context):
violations.append(m.group())
if violations:
print("⚠️ [콘텐츠 품질] 실측치로 읽힐 수 있는 표현이 포함되어 있을 가능성:")
for v in violations:
print(f" - {v}")
print(" 근거가 있는 경우 「시산」, 「이론치」 등의 면책 문구를 근처에 추가할 것.")
EOF
exit 0 # 차단하지 않음 (경고만 표시)


### settings.json 에 추가

```json
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "bash ~/.claude/hooks/content_quality_guard.sh",
"timeout": 5
}
]
}
]

matcher: "Write|Edit"

...

# harness_audit.sh 의 스킬 체크 부분 (추가분)
SKILL_DIR="$HOME/.claude/skills"
REPORT_FILE="$HOME/.claude/audit/skill-audit-report.md"
ISSUES=0
echo "# 스킬 감사 보고서 $(date +%Y-%m-%d)" > "$REPORT_FILE"
for skill_file in "$SKILL_DIR"/**/*.md; do
    skill_name=$(basename "$skill_file")
    # 체크 1: Do NOT use 구문의 유무
    if ! grep -q "Do NOT use" "$skill_file"; then
        echo "- ⚠️ $skill_name: description 에 'Do NOT use' 구문 없음" >> "$REPORT_FILE"
        ISSUES=$((ISSUES + 1))
    fi
    # 체크 2: 90일 이상 업데이트 없음 (git log로 확인)
    recent=$(git -C "$HOME/.claude" log --follow --since="90 days ago" --oneline -- "$skill_file" 2>/dev/null | wc -l | tr -d '[:space:]')
    if [[ "${recent:-0}" -eq 0 ]]; then
        echo "- 📅 $skill_name: 90일 이상 업데이트 없음 (노후화 가능성)" >> "$REPORT_FILE"
        ISSUES=$((ISSUES + 1))
    fi
done
echo "" >> "$REPORT_FILE"
echo "**문제 건수: $ISSUES**" >> "$REPORT_FILE"

후크 1과의 연계

...

harness_inject.sh 에 추가

AUDIT_REPORT="$HOME/.claude/audit/skill-audit-report.md"
if [[ -f "$AUDIT_REPORT" ]]; then
issues=$(grep "問題件数:" "$AUDIT_REPORT" | grep -o '[0-9]+')
if [[ "${issues:-0}" -gt 0 ]]; then
echo "⚠️ 스킬 감사: ${issues}건의 문제가 검출되었습니다 (audit/skill-audit-report.md 참조)"
fi
fi


이로써 「세션 종료 → 감사 → 다음 세션 시작 시 문제 건수 주입」이 자동화된다.

...

[세션 시작]
└─ UserPromptSubmit hook
├─ feedback.md 마지막 3개 엔트리 주입
└─ skill-audit-report.md의 문제 건수 주입
[작업 중]
└─ PostToolUse hook (Write 후)
└─ articles/*.md를 작성할 때마다 콘텐츠 품질 스캔
[세션 종료]
└─ Stop hook
├─ 세션 요약 생성 (기존)
└─ 스킬 감사 실행 → skill-audit-report.md에 저장


하네스(Harness)가 자기 자신의 문제를 검출하여 다음 세션으로 전달한다. Claude가 매일 아침 「어제의 문제 리스트」를 받고 작업을 시작하는 느낌에 가깝다.

## 구현 시 주의사항

**후크(Hook)의 실패는 사이런트(Silent)하게 통과시킨다**

후크가 에러로 인해 중단되면 세션 시작 및 종료가 차단된다. 각 스크립트의 서두에 `set +e`를 넣거나, 명령어에 `|| true`를 붙여 방어한다.

**주입하는 컨텍스트(Context)는 최소한으로 한다**

`feedback.md`의 전문을 주입하는 것은 토큰 소비가 크다. 「제목 + How to apply」 2줄만 추출하는 것이 현실적인 타협점이다.

**대상 파일 필터는 엄격하게 설정한다**

콘텐츠 품질 체크를 하네스 파일 자체(`hooks/`, `skills/`)에 적용하면 오검출이 많아진다. `articles/` 하위 디렉토리만을 대상으로 함으로써 정확도를 유지할 수 있다.

## 요약

- `feedback.md`에 쓰는 것만으로는 다음 세션에 전달되지 않음 → UserPromptSubmit에서 주입함
- Write 후의 후크로 콘텐츠 품질을 즉시 체크함 (차단하지 않고 경고만 수행)
- Stop 후크에 스킬 노후화 체크를 추가하여 결과를 다음 세션으로 전달
- 이 세 가지가 결합되면 「하네스가 자기 자신을 수복하는 루프」가 된다

감상이나 "이 케이스는 어떻게 하나요?"와 같은 질문은 댓글로 편하게 남겨주시면 감사하겠습니다. '좋아요'도 큰 힘이 됩니다.

## 저자 코멘트

### Discussion

![](https://static.zenn.studio/images/drawing/discussion.png)

AI 자동 생성 콘텐츠

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

원문 바로가기
0

댓글

0