hook은 이미 설정되어 있고 /hooks 명령으로 보이기도 합니다. 그런데 그다음부터 이상해집니다. 요청을 보냈는데 requests.log에 새 줄이 나타나지 않습니다. 아니면 두 번 나타납니다. 더 나쁜 경우도 있습니다. 수정할 때마다 자동 동작이 실행되어 평소 작업을 방해하기 시작합니다.
이럴 때는 설정을 처음부터 다시 만들 필요가 없습니다. 관찰한 내용을 작고 검증 가능한 과제로 바꾸는 편이 낫습니다. 무엇이 hook을 실행시켜야 하는지, 어떤 흔적이 남아야 하는지, 어떤 경우는 반드시 건너뛰어야 하는지를 정리하는 것입니다. 그러면 Claude Code가 특정 파일을 확인하고 최소한의 수정을 제안할 수 있습니다.

hook이 실행되어야 하는 시점
이 글에서는 설정 파일에 적힌 커맨드 hook을 다룹니다. 이런 hook에는 세 부분이 중요합니다. 이벤트, 실행 조건, 핸들러입니다. 이벤트는 "언제 볼 것인가"에 답하고, 조건은 해당하는 경우를 좁히며, 핸들러가 동작을 실행합니다.
예를 들어 요청 로그는 UserPromptSubmit을 구독합니다. 이 이벤트는 세션에 요청이 들어왔을 때, 아직 모델이 처리하기 전에 발생합니다. 대부분은 여러분이 보낸 요청이지만, Claude Code는 스스로 시작하는 턴에서도 이 이벤트를 발생시킵니다. /loop의 다음 회차나 백그라운드 서브에이전트의 보고가 그렇습니다. 그래서 로그에 새 줄이 생겼다는 것만으로는 확인되는 것이 거의 없습니다. hook이 요청을 받아서 기록했다는 사실뿐, 그 이상은 아닙니다. 에이전트가 과제를 올바르게 수행했는지는 이 한 줄로는 알 수 없습니다.
UserPromptSubmit에는 matcher가 없습니다. 로그에 일부 요청만 필요하다면 핸들러 자체가 걸러내야 합니다. 도구 이벤트는 상황이 다릅니다. 예를 들어 matcher Edit|Write는 도구를 선택할 뿐이고, 파일 경로는 호출 데이터에서 따로 확인합니다. 도구 이름으로 된 matcher가 "src 안의 파일만"을 뜻하지는 않습니다.
Stop 이벤트는 Claude가 응답을 끝냈다는 뜻입니다. 알림을 보내기에는 편리한 시점이지만, 코드가 맞았다거나 과제가 성공적으로 끝났다는 증거는 아닙니다.
수정하기 전에 짧은 시나리오 두 가지를 적어 두세요.
- 필요한 경우: 테스트 요청을 보내고
requests.log에 새 줄이 하나 생기는지 확인합니다. - 불필요한 경우: Claude Code에 새 요청을 보내지 않고 일반 에디터에서 파일을 열어 로그가 바뀌지 않는지 확인합니다. 모든 요청을 기록하는 로그라면 두 번째로 보낸 prompt에도 기록이 남아야 하므로, 이는 네거티브 케이스가 될 수 없습니다.
이것이 검증 기준입니다. Claude Code의 구체적인 답을 미리 정해 둘 필요는 없습니다.
기록이 나타나지 않는 경우
관찰 결과가 기대와 다른 지점부터 시작하세요. 로그라면 테스트 요청 후 새 줄이 생기지 않은 것이 그 지점입니다. 이제 설정에서 동작 쪽으로 따라가 보세요.
- Claude Code 인터랙티브 세션에서
/hooks를 입력하고 원하는 항목이 로드되었는지 확인합니다. 이 메뉴는 여러분이 직접 여는 것이므로, 확인한 결과를 에이전트에게 전달하세요. - 이벤트를 대조하세요. 요청 로그는
UserPromptSubmit에 반응해야 하고, 파일 변경 후 동작은 보통 도구 이벤트에 연결되어 있습니다. - 원본 설정 파일을 찾으세요. 사용자, 프로젝트, 로컬 설정 중 하나일 수 있습니다. 이름만이 아니라 전체 명령어까지 비교하세요. 같은 핸들러가 여러 설정 파일에 동시에 있다고 해서 그 자체로 두 번 실행된다는 뜻은 아닙니다.
- 핸들러 명령을 확인하세요. 파일이 존재하는지, 명령이 사용 중인 OS에 맞는지, 공백이 포함된 경로가 깨지지 않았는지 살펴보세요.
- 결과가 저장되는 위치를 확인하세요. 상대 경로는 프로세스의 작업 디렉터리에 따라 달라지므로, 핸들러가 여러분이 찾는 곳이 아닌 다른 곳에 파일을 썼을 수 있습니다.
여기서 주된 경로는 에이전트를 거치는 것입니다. 관찰 내용, /hooks 확인 결과, 수정 범위를 전달하세요.
UserPromptSubmit hook이 /hooks에는 보이는데, 테스트 요청 후
requests.log에 새 줄이 없어.
원본 설정과 핸들러를 찾아 줘. 이벤트, 명령 경로,
작업 디렉터리, 로그용 폴더 생성 여부를 확인해 줘. 원인을 보여 주고
최소한으로 수정해 줘. 나머지 hook과 설정은 그대로 둬.
수정 후 필요한 시나리오와 네거티브 시나리오를 설명해 줘.에이전트에게 어떤 파일의 정확히 어느 줄을 바꾸는지 알려 달라고 요청하세요. 그러면 원본 설정을 고치는 것인지, 아예 로드되지도 않는 엉뚱한 사본을 고치는 것인지 확인할 수 있습니다.
예를 들어 에이전트가 이렇게 밝혀냈다고 합시다. 핸들러는 logs/requests.log에 씁니다. 그런데 logs/ 폴더가 없고 스크립트도 이를 만들지 않습니다. 최소한의 수정은 기록 전에 이 폴더를 만드는 것입니다. 수정 후에는 정확히 logs/requests.log를 확인하세요. 테스트 요청 하나에 기록 하나가 추가되어야 합니다. 이는 가능한 원인 하나의 예시일 뿐, 로그가 비어 있을 때마다 들어맞는 진단은 아닙니다.
설정이 파일에는 있는데 /hooks에는 없다면, 원인은 JSON 문법, 설정 범위일 수도 있고, 지금 hook이 아예 실행되지 않는 상황일 수도 있습니다. 프로젝트 폴더가 신뢰할 수 있는 폴더로 표시되지 않았거나, disableAllHooks가 켜져 있거나, 관리자가 관리형 정책의 hook만 허용한 경우입니다. 여러 설정 파일의 hook은 서로 대체되지 않고 합쳐집니다. 항목이 로드되었는데 흔적이 없다면, 주목할 곳은 명령, 입력 데이터, 출력 경로로 옮겨갑니다.
이 분기의 요점은 간단합니다. Claude Code가 항목을 로드하기 전까지는 스크립트 동작을 따지기에 이릅니다. 항목이 보이면 설정이 이미 애플리케이션까지 도달한 것이므로, 핸들러 실행, 입력 JSON, 결과가 나타날 위치를 확인하는 편이 유용해집니다.
핸들러가 오류로 종료된 경우
커맨드 hook은 표준 입력으로 JSON을 받습니다. 여기에는 공통 이벤트 필드와 해당 이벤트 고유의 데이터가 들어 있습니다. UserPromptSubmit의 경우 요청 텍스트는 prompt 필드로 들어옵니다. 이 텍스트는 데이터로 다뤄야 합니다. 셸 명령과 이어 붙여 실행해서는 안 됩니다. 테스트 요청에는 따옴표, 줄바꿈, 그 밖의 어떤 일반 텍스트도 들어 있을 수 있습니다.
Claude Code에게 가상의 입력으로 핸들러 실행을 재현해 달라고 요청하세요. 요청에 중요한 경우를 바로 명시하세요.
세션과 별도로 가상의 JSON으로 hook 핸들러를 확인해 줘.
확인할 케이스: 일반 prompt와 여러 줄 prompt, 공백이 있는 프로젝트 경로,
로그 폴더가 없는 경우, 잘못된 입력.
prompt 텍스트를 코드로 실행하지 마. 실행 명령, 종료 코드,
stderr, 테스트 파일의 실제 변경 내용을 보여 줘. 실제 로그는 건드리지 마.종료 코드는 핸들러 자체의 결과를 이해하는 데 도움이 되지만, 그 의미는 이벤트에 따라 달라집니다. exit 0은 hook 명령이 오류 없이 끝났다는 뜻입니다. 이것으로 과제 전체의 결과까지 확인되지는 않습니다. exit 2는 "사전" 이벤트에서는 동작을 막을 수 있지만, 이미 실행된 도구 이후에는 그 동작을 취소하지 못합니다. 그러니 exit 2를 쓰는 방식 하나를 여러 이벤트에 무작정 옮겨 쓸 수는 없습니다.
독립 실행은 성공했는데 세션에서는 여전히 hook이 조용하다면, 프로젝트 폴더에서 claude --debug-file hook-debug.log 명령으로 Claude Code를 실행하고 테스트 요청을 한 번 더 보내세요. 생성된 hook-debug.log를 열어 이벤트, 일치한 hook들, 종료 코드를 찾으세요. 이는 여러분의 requests.log와는 별개인, Claude Code 자체의 로그입니다. 확인이 끝나면 이 진단 세션을 종료하고, 평소 작업에는 플래그 없이 Claude Code를 실행하세요. 대부분의 이벤트에서 일반 stdout은 debug log로 가고, transcript에 반드시 나타나지는 않습니다. 예외는 이벤트마다 다르므로, 임의의 진단용 문자열을 stdout에 출력하면 편리한 로그가 되는 대신 컨텍스트에 영향을 줄 수 있습니다.
처음 살펴볼 때는 진단용 표시를 별도의 로컬 파일에 적었다가, 확인이 끝나면 지우는 편이 안전합니다. 공식 문서에는 hooks용 범용 dry-run 플래그가 없습니다. 시험 모드는 특정 학습용 핸들러에서 직접 구현하는 부분입니다.
hook이 불필요하게 여러 번 실행되는 경우
한 번의 요청 후 똑같은 줄이 두 개 생겼다면 중복 등록을 의심할 수 있습니다. 예를 들어 사용자 설정과 프로젝트 설정에 비슷하지만 완전히 같지는 않은 hook이 들어 있는 경우입니다. 완전히 같은 명령이 여러 파일에 그대로 반복되어 있는 것뿐이라면, 그래도 실행은 한 번만 됩니다. plugin이나 skill에서 온 사본은 별도로 취급되므로 메뉴에 표시되는 출처도 중요합니다. 먼저 /hooks 결과를 에이전트에게 전달하고, 접근 가능한 모든 설정 파일에서 해당 이벤트의 항목을 나열해 달라고 요청하세요. 설정에 문제가 없다면, 핸들러 자체가 한 번의 실행에서 두 번 쓰고 있지는 않은지 확인하세요. 처음 발견한 줄을 바로 지우는 것은 이릅니다.
또 다른 흔한 원인은 조건이 지나치게 넓은 경우입니다. 예를 들어 hook이 src/ 안의 파일이 바뀐 뒤에만 진단용 표시를 남겨야 하는데, Edit와 Write를 쓸 때마다 반응한다고 합시다. 여기에는 두 단계의 필터링이 필요합니다.
- matcher가
Edit와Write도구를 선택하고; - 별도의 조건이 도구 인자에서 경로를 확인해
src/밖의 모든 것을 걸러냅니다.
한 검사가 다른 검사를 대신하지 않습니다. matcher가 맞지 않는 도구를 먼저 걸러내고, 그다음 인자 기반 조건이 src/ 안의 경로만 남깁니다. 진단용 표시는 두 검사가 모두 "예"라고 답했을 때만 나타납니다.
설정 조각은 다음과 같은 모습일 수 있습니다.
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "python .claude/hooks/mark-source-edit.py" }
]
}
]이것은 기존 settings.json 안 hooks 객체의 한 항목이며, 완전한 JSON 문서가 아닙니다. 경로 확인은 핸들러 안에서 해야 합니다. 여기서 mark-source-edit.py는 예시일 뿐이며 본문 안에 그 내용은 없습니다. 이 명령은 python을 쓸 수 있고 프로젝트 루트에서 실행된다고 가정합니다. 여러분의 설정에서 인터프리터, 셸, 경로를 에이전트가 확인해야 하며, 이 예시를 바로 쓸 수 있는 설정으로 여기고 그대로 복사해서는 안 됩니다.
이 조각은 하나의 문장처럼 읽으면 이해하기 쉽습니다.
- 파일 수정이 성공한 후
- 도구가 Edit 또는 Write라면
- mark-source-edit.py 실행
이 흐름은 설정에 적힌 내용만 보여줍니다. "표시를 남길지 파일을 건너뛸지"는 경로를 확인한 뒤 핸들러가 결정합니다. 그러므로 matcher가 있다는 사실은 도구 선택을 확인해 줄 뿐, src/에 대한 올바른 필터링까지 증명하지는 않습니다.
| 동작 | 기대하는 흔적 | 무엇을 확인하는가 |
|---|---|---|
Edit로 src/card.js 수정 | 새 진단용 표시 하나 | 필요한 도구와 필요한 경로가 조건을 통과 |
Write로 src/theme.css 생성 | 새 진단용 표시 하나 | 두 번째로 허용된 동작도 반영됨 |
Edit로 README.md 수정 | 새 표시 없음 | src/ 밖의 파일이 제외됨 |
이 표에서 Edit와 Write는 Claude Code의 도구를 뜻합니다. 에디터에서 수동으로 파일을 수정하면 이 도구들이 호출되지 않으므로, 이런 방식으로는 matcher를 검증할 수 없습니다. 각 동작 전에 진단용 파일의 끝을 표시해 두고, 이후 새로 추가된 항목만 비교하세요.
경계가 아직 검증되지 않았다면 동작은 무해하게 유지하세요. 실행 표시 하나만 남기고, 포맷팅이나 차단이나 대량 파일 수정은 하지 마세요. 먼저 필요한 경우가 정확히 한 번만 통과하고 불필요한 경우는 통과하지 않는지 확인하세요.
불필요한 실행은 언제나 작업을 늘립니다. 다만 파일을 쓰기만 하는 로컬 명령 자체는 모델을 호출하지 않습니다. prompt나 agent 타입의 hook은 다르게 동작하며 모델을 사용합니다. 비용에 대한 자세한 분석은 여기서 필요하지 않고, 이 두 종류의 핸들러를 혼동하지만 않으면 됩니다.
반응을 끄는 방법
hook이 지금 당장 작업을 방해한다면, 원본 항목을 찾아 해당 이벤트 배열에서 그것만 지우세요. 나머지 hook은 그대로 두세요. 이것은 앞으로의 실행을 막는 것입니다. 이미 실행된 명령을 취소하지도, 변경된 파일을 되돌리지도, 로그에 이미 기록된 줄을 지우지도 않습니다.
나중에 되돌릴 생각이라면 삭제 전에 선택한 항목의 사본을 남겨 두세요. 변경 후 다시 /hooks를 열어 해당 항목이 더 이상 없는지 확인하세요. 메뉴가 여전히 이전 설정을 보여준다면 세션을 종료하고 같은 프로젝트에서 새 세션을 여세요. 그다음 확인용 동작을 실행하고 전후 로그를 비교하세요. 꺼진 hook에서 새 줄이 생기면 안 됩니다.
모든 hook을 일시적으로 끄는 설정으로 "disableAllHooks": true가 있습니다. 하지만 이것은 전체를 한 번에 끄는 마스터 스위치이지, 항목 하나만 끄는 방법이 아닙니다. 이 설정 자체에는 설정 파일의 우선순위가 적용됩니다. 프로젝트 파일의 "disableAllHooks": false는 여러분의 사용자 설정에 있는 true를 덮어씁니다. 또한 조직의 관리형 정책으로 지정된 hook은 일반 사용자 설정이나 프로젝트 설정으로는 끌 수 없습니다.
꺼진 hook은 앞으로 실행되지 않습니다. 핸들러가 이미 파일을 바꿨다면, 그 파일을 복구하는 작업은 별도로, diff나 저장된 버전을 확인한 후에만 해야 합니다.
Claude Code에 수정을 맡기는 방법
좋은 지시에는 관찰 내용, 예상 이벤트, 구체적인 파일, 수정 범위가 들어갑니다. 이 정도면 에이전트가 자동화 전체를 다시 짜지 않고도 설정을 조사할 수 있습니다.
이 프로젝트의 hook 문제를 해결해 줘.
관찰: 테스트 요청 한 번에 requests.log에 두 줄이 생겨.
이 세션에서는 /loop도 백그라운드 서브에이전트도 실행하지 않았어.
기대: UserPromptSubmit은 정확히 한 줄만 추가해야 해.
/hooks 결과를 첨부할게. 사용 가능한 모든 설정 출처와
핸들러 명령을 확인해 줘.
먼저 원인과 수정해야 할 파일을 알려 줘. 그다음
최소한으로 수정하고, 나머지 hook은 그대로 두고, diff를 보여 줘.
필요한 경우와 네거티브 케이스를 확인해 줘. 마지막으로
이 반응만 끄는 방법을 보여 줘.마지막에는 명확한 답 네 가지가 남아 있어야 합니다.
- 어떤 이벤트가 hook을 실행시키는지;
- 어떤 조건이 불필요한 경우를 걸러내는지;
- 원본 설정과 핸들러가 어디에 있는지;
- 나머지에 영향을 주지 않고 이 반응만 끄는 방법.
그리고 전체 점검 경로도 있습니다. 필요한 동작은 예상되는 흔적 하나를 남기고, 불필요한 동작은 흔적을 남기지 않으며, 핸들러 오류는 타깃 진단에서 보이고, 끄고 나면 새 흔적이 더 이상 생기지 않습니다. 상당히 현실적인 이야기입니다. 그래서 이런 hook은 "가끔 뭔가를 하는" 자동화보다 유지하기가 더 쉽습니다.
동작을 확인할 때는 공식 hooks 레퍼런스와 실전 가이드를 활용하세요. 인터페이스는 Claude Code와 함께 계속 바뀝니다.
동작은 2026년 9월 18일 기준 공식 문서와 대조했습니다. 예시는 확인 순서를 보여줄 뿐, 완성된 핸들러는 아닙니다. 명령과 경로는 각자의 OS와 셸에서 직접 확인하세요.
GO TO FULL VERSION