Claude Code는 ~/.claude/settings.json에 후크(hooks)를 등록함으로써, 특정 이벤트를 트리거로 임의의 셸 커맨드(shell command)를 자동 실행할 수 있습니다.
하지만 후크 설정이 늘어나면 「의도하지 않은 타이밍에 발화하는」 오검출(false positive)이 조용히 쌓여갑니다.
본 기사에서는 아래의 두 가지 메커니즘을 조합하여 하네스(harness)를 자율 개선시킨 사례를 소개합니다.
월간 스캔 (spec.md 리뷰): 30일마다 설정 파일을 구조 리뷰하여 괴리 및 열화를 검출
GPT-5.4에 의한 코드 리뷰: codex-review 스킬을 사용하여 type: "prompt" 후크의 오검출을 발견
settings.json의 hooks 섹션은 다음과 같은 구조를 가집니다.
{
"hooks": {
"UserPromptSubmit": [
...
후크에는 여러 가지 type이 있습니다.
| type | 동작 |
|---|
command | 셸 커맨드(shell command)를 실행. exit 1로 Claude의 처리를 중단할 수 있음 |
prompt | Claude 자신에게 추가 프롬프트(prompt)를 삽입 (이 부분이 함정) |
type: "prompt"는 「사용자의 입력에 지시를 덮어쓰고 싶을 때」 사용합니다. 예를 들어 「git push를 포함한 지시가 오면, 정말 의도한 것인지 확인하게 한다」와 같은 사용법이 상정됩니다.
필자의 환경에서는 스케줄 태스크(scheduled task)의 자동 기동이나 스킬 관리의 일련의 플로우를 「하네스(harness)」라고 부르며 운용하고 있습니다.
어느 월간 스캔에서, UserPromptSubmit에 등록한 type: "prompt" 후크가 의도하지 않은 문자열 패턴에도 반응하는 문제를 발견했습니다.
구체적으로는 다음과 같은 matcher를 설정하고 있었습니다.
{
"matcher": "deploy",
"hooks": [
...
이 설정의 의도는 「deploy라는 단어를 포함하는 프롬프트가 오면 배포 확인을 촉구한다」는 것입니다.
하지만 실제로는 다음과 같이 전혀 관계없는 문맥에서도 발화하고 있었습니다.
- 「Cloudflare Workers의 배포 전략을 알려줘」
- 「deploy hook의 동작을 확인하고 싶어」
- 「
vercel deploy의 문서 URL을 조사해줘」
type: "prompt"는 type: "command"와 달리, 추가 프롬프트가 조용히 삽입될 뿐이므로 사용자가 알아차리기 어렵다는 점이 까다롭습니다. Claude의 답변이 미세하게 변해도 「그런가 보다」 하고 넘어가 버리게 됩니다.
월간 스캔 플로우에서는 /codex-review 스킬을 사용하여 settings.json의 차이점(diff)을 GPT-5.4가 리뷰하도록 하고 있습니다.
# codex-review 스킬 호출 예시
cd ~/.claude
codex --model gpt-5.4 "settings.json의 hooks 섹션을 리뷰하고, 오검출 리스크가 있는 matcher를 지적해 주세요"
GPT-5.4는 다음과 같은 리포트를 출력했습니다 (요약).
[HIGH] matcher: "deploy" — 부분 일치(partial match)이므로, 단어 "deploy"를 포함하는
모든 프롬프트에 발화합니다.
의도하는 대상이 "실제 배포 조작"이라면,
...
이 지적을 바탕으로, deploy의 matcher를 구체적인 커맨드 이름으로 분할했습니다.
{
"hooks": {
"UserPromptSubmit": [
...
오검출을 방지하는 지속적인 메커니즘으로서, **「변경 의도의 spec.md」**를 후크와 나란히 관리하고 있습니다.
~/.claude/
settings.json ← 실제 설정
docs/
...
hooks-spec.md에는 다음과 같이 기술합니다.
## UserPromptSubmit / vercel deploy
**목적**: vercel deploy 커맨드를 포함한 지시가 왔을 때, 프로덕션(production) 배포 전에 스테이징(staging) 확인을 촉구함
**상정 발화**:
...
월간 스캔에서는 이 spec.md와 settings.json을 [비교합니다/사용합니다]
의 diff를 가져와서, "명세서에 적혀 있지 않은 Hook이 늘어나지 않았는지", "30일 이상 업데이트되지 않은 Hook이 실제 상태와 일치하는지"를 체크합니다.
스케줄 태스크(Scheduled Task)로 등록할 스캔의 골격을 보여줍니다.
import json
from pathlib import Path
from datetime import datetime, timedelta
...
type: "prompt"
Hook은 조용히 삽입되기 때문에, 오검출(False Positive)을 알아차리기 어렵습니다 -
matcher
는 **구체적인 커맨드 이름(Command Name)**까지 좁힙니다 (단어 단위는 오검출의 원인이 됩니다) - Hook과 병행하여
spec.md (변경 의도의 명세서)
를 관리함으로써, 월간 스캔이 기능하게 됩니다 - GPT-5.4 등의 외부 리뷰어에게
settings.json
을 정기적으로 리뷰하게 하면, 스스로는 알아차리지 못하는 오검출 패턴을 발견할 수 있습니다 - 스캔 결과는
settings.json
과의 diff로 시각화하여, 미기재된 Hook을 자동 검출합니다.
하네스(Harness)는 만들고 끝나는 것이 아니라, 정기적으로 "의도와 구현의 괴리"를 체크하는 메커니즘을 가짐으로써, 장기간에 걸쳐 건전하게 계속 기능합니다.