코딩 에이전트: 조용히 돌봐주는 훅과 시끄럽게 가르치는 훅의 차이
요약
코딩 에이전트 설정 시 사용하는 두 가지 훅(hook) 방식인 '조용한 수정'과 '시끄러운 실패'의 차이점을 설명합니다. 에이전트의 학습과 정확성을 높이기 위해 단순 포맷팅 외의 규칙 위반에는 에러를 반환하여 에이전트가 스스로 교정하도록 유도해야 함을 강조합니다.
핵심 포인트
- 조용한 수정(silent-fix)은 에이전트가 오류를 인지하지 못해 잘못된 패턴을 반복하게 만듦
- 시끄러운 실패(fail-loudly)는 에러 메시지를 컨텍스트에 전달하여 에이전트의 학습을 유도함
- 기계적이고 명확한 수정(예: Prettier)은 조용한 방식이 적합함
- 에이전트의 지식이 필요한 복잡한 규칙 위반은 반드시 시끄러운 실패 방식을 사용해야 함
안녕 친구들
실제 코드베이스에서 코딩 에이전트(coding agents)를 설정하는 시리즈의 세 번째 포스트입니다 (파트 1: CLAUDE.md 구조화, 기술 및 에이전트, 파트 2: 기술 설명). 이번 포스트는 훅(hooks)에 관한 것이며, 수정하는 훅과 가르치는 훅의 차이점에 대해 다룹니다.
두 가지 종류의 훅
훅은 에이전트의 동작에 실행되는 코드이며, 절대 건너뛰어서는 안 되는 규칙을 적용하는 표면입니다. 지침(Instructions)은 합리화되어 무시될 수 있지만 ("그냥 파일 이름일 뿐이야, 이 정도면 충분해"), 훅은 그럴 수 없습니다.
하지만 훅에는 두 가지 종류가 있습니다:
- 조용한 수정 (silent-fix) 훅은 출력을 자동으로 복구합니다. 에이전트는 에러를 전혀 보지 못하므로, 잘못된 패턴을 영원히 계속 생성하게 됩니다.
- 시끄러운 실패 (fail-loudly) 훅은 차단하고 에러를 반환합니다. 이 메시지는 에이전트의 컨텍스트(context)에 전달됩니다 - 한 번의 실패가 곧 하나의 교훈이 됩니다.
수정이 기계적이고 항상 정확할 때는 조용한 방식도 괜찮습니다. 예를 들어, 매 턴마다 훅을 통해 prettier를 실행하는 경우, 에이전트가 포맷팅을 배울 필요는 없으며 아무도 신경 쓰지 않습니다. 흥미로운 사례는 수정에 훅이 가지고 있지 않은 지식이 필요할 때입니다. 이 경우라면 시끄러운 방식이 필수적입니다.
Postgres 마이그레이션 훅
우리의 마이그레이션은 Flyway를 사용하며, 파일 이름이 곧 API입니다:
V012__Billing_AddInvoiceIndex.sql
실수를 하더라도 로컬 머신에서는 아무것도 충돌하지 않습니다. Flyway가 단순히 파일을 건너뛰거나 순서를 잘못 지정할 뿐이며, 배포 단계에서야 이를 알게 됩니다. 이는 에이전트가 위반하기 딱 좋은 종류의 규칙입니다. SQL은 완벽하지만, 파일 이름은 add_invoice_index.sql인 식입니다.
훅이 조용히 이름을 바꿀 수 있을까요? 아니요. 이름을 바꾸려면 다음 버전 번호, 올바른 스키마(schema), 설명 등이 필요한데, 이는 에이전트 측에 있는 지식입니다. 여기서 조용한 수정은 교육적으로 더 나쁠 뿐만 아니라, 정확성도 떨어집니다. 따라서 훅의 역할은 거부하고, 그 이유를 말하는 것입니다:
#!/usr/bin/env bash
# Write|Edit에 대한 PreToolUse 훅: Flyway 마이그레이션 명명 규칙 강제.
set -euo pipefail
...
settings.json에 연결된 모습:
{
"hooks": {
"PreToolUse": [
...
무엇이 다음 행동을 할지(What happens next)가 핵심입니다. 쓰기 작업은 차단되고, 에이전트는 규칙을 읽고, 기존 파일을 확인하여 다음 버전을 찾고, V013__...로 이름을 바꾸며, 세션의 나머지 부분에 걸쳐 모든 마이그레이션을 올바르게 명명합니다. 이 위반 사항을 통해 학습했습니다.
정확한 해결책 (The fix, precisely)
"크게 실패하기(Fail loudly)"는 세 가지 패턴 중 하나를 선택하는 것이며, 그 결정은 다음 표에 들어맞습니다:
| 패턴 | 메커니즘 (Claude Code) | 사용 시점 |
|---|---|---|
| 조용한 수정 (Silent fix) | apply fix, exit 0 | 수정 사항이 기계적이고 항상 정확할 때 (포맷팅 등) |
| ... | ||
Exit 코드에 대한 설명입니다. 사람들은 이 부분에서 실수를 합니다: exit 0은 허용하며, 에이전트는 아무것도 보지 못합니다. exit 2는 표준 오류(stderr)를 에이전트에게 다시 전달합니다. PreToolUse 단계에서는 액션을 취소할 뿐만 아니라; PostToolUse 단계에서는 이미 액션이 발생했으므로, 단지 교훈을 제공합니다. 다른 모든 exit 코드는 표준 오류를 사람에게만 보여줍니다: 에이전트는 아무것도 배우지 못하며, 이는 규칙에 대한 최악의 선택입니다. |
조용한 자동 수정(silent autofix)을 **크게 실패하는 수정(loud fix)**으로 변환하는 것은 추가적인 분기 처리 하나가 필요합니다:
# 이전 - 에이전트가 절대 학습하지 못하고, 훅은 영원히 청소만 함
elint --fix "$file" >/dev/null 2>&1
exit 0
# 이후 - 동일한 수정이지만, 에이전트는 실수를 멈춤
before=$(git hash-object "$file")
elint --fix "$file" >/dev/null 2>&1
...
이것이 전체 해결책입니다: 변경 사항이 있었는지 감지하고, 만약 있었다면 표준 오류(stderr)로 알리고 exit 2를 반환하는 것입니다.
위의 마이그레이션 훅은 크게 거부하는 (loud reject) 템플릿입니다: 경로와 일치하는지 확인하고, 유효성을 검사하며, 규칙과 유효한 예시와 함께 거부합니다. 정규 표현식(regex)과 메시지를 교체하면 브랜치 이름, 커밋 형식 등 팀이 리뷰에서 반복적으로 지적하는 모든 것에 대해 동일한 강제 적용을 할 수 있습니다.
오류 메시지는 교육의 표면이다 (The error message is a teaching surface)
거부 메시지(rejection message)는 대부분의 훅이 실패하는 부분입니다. 여기에는 네 가지가 필요합니다:
- 무엇이 차단되었는가 (What was blocked) - 모호함이 없는 정확한 파일명
- 규칙 (The rule) - 단순히 "유효하지 않은 이름"이 아닌, 구체적인 형식 사양 (format spec)
- 유효한 예시 (A valid example) - 에이전트는 패턴 매칭 (pattern-match)을 합니다. 예시 하나가 세 단락의 설명보다 효과적입니다.
- 다음에 확인할 곳 (Where to look next) - "다음 버전 번호를 확인하기 위해 기존 파일들을 확인하세요"
단순히 error: invalid file이라고만 말하는 훅은 차단은 하지만 가르치지는 못합니다. 에이전트는 추측하며 다양한 변형을 재시도하고 토큰을 낭비하게 됩니다. stderr (표준 에러)를 좋은 동료가 남긴 리뷰 코멘트처럼 작성하세요.
반복되는 거절은 당신의 문서에 대한 버그 리포트입니다
위반하고, 거절당하고, 수정하고, 잊어버리는 이 루프가 매 세션마다 반복되어도 괜찮을까요? 아니요. 교훈은 컨텍스트 윈도우 (context window)와 함께 사라집니다. 세션마다 반복해서 발생하는 거절은 당신의 지시 계층 (instruction layer)이 실패했음을 의미합니다. 문서와 기술 (skills)은 예방 (prevention) (동작 이전에 로드됨)이며, 훅은 **보장 (guarantee)**입니다. 그리고 훅이 실행되는 것은 예방 조치가 어떻게 작동하고 있는지에 대한 텔레메트리 (telemetry)입니다.
가끔 실행된다면: 안전장치가 제 역할을 하고 있는 것입니다. 반복해서 실행된다면: 당신의 문서에 대한 실패하는 테스트입니다. 세 가지 일반적인 원인은 다음과 같습니다:
- 규칙이 동작 이전에 로드되는 어떤 접점 (surface)에도 없음 -> 해당 작업을 소유한 기술 (skill)에 규칙을 추가하세요 (우리의 마이그레이션 규칙은 단순히 훅에 있는 것이 아니라 마이그레이션 기술에 있어야 합니다).
- 규칙은 존재하지만 기술이 트리거되지 않음 -> 라우팅 (routing) 버그입니다. 기술 설명 (skill description)을 수정하세요 (이전 포스트).
- 규칙이 CLAUDE.md에 있지만 텍스트 더미 속에 파묻혀 있음 -> 이를 강조하거나, 짧게 만들거나, 노이즈를 밖으로 옮기세요.
한 줄의 bash 명령어로 훅을 텔레메트리로 바꿀 수 있습니다 - exit 2 이전에 로그를 남기세요:
echo "$(date +%F) $name" >> "$CLAUDE_PROJECT_DIR/.claude/hook-rejections.log"
해당 파일에서 가장 많이 실행된 규칙들이 바로 수정할 가치가 가장 높은 문서들입니다. 따라서 "훅을 만들 것인가, 기술을 수정할 것인가?"는 잘못된 질문입니다. 시간 순서에 따라 둘 다 해야 합니다: 즉시 훅을 만들고, 그 다음 반복되는 실행 결과가 어떤 문서 수정이 가치 있는지를 알려주게 하세요.
시끄럽게 할 것인가, 조용히 할 것인가 - 체크리스트
- 수정 사항이 기계적이고, 항상 정확하며, 반복되어도 아무도 신경 쓰지 않는 경우 (포맷팅)? 조용한 수정 (Silent fix).
- 자동화하기에 안전하지만, 해당 패턴을 보는 것에 지쳤을 경우? 시끄러운 수정 (Loud fix).
- 수정에 지식이나 판단이 필요한 경우 (명명 (naming), 버전 관리 (versioning), 스키마 (schema))? 시끄러운 거부 (Loud reject).
- 위반 사항이 비용이 많이 들거나 하류 단계(downstream)에서 되돌릴 수 없는 경우 (운영 환경 배포 (prod deploy), 데이터 마이그레이션 (data migration))? 항상 시끄러운 거부 (Loud reject).
기억해야 할 한 문장: 조용한 훅은 보살피고(babysits), 시끄러운 훅은 가르칩니다(teaches). 공백(whitespace)은 보살피고, 그 외의 모든 것은 가르치세요.
도움이 되었기를 바랍니다!
Hash
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기