Claude Code hooks 레퍼런스는 현재 SessionStart부터 ElicitationResult까지 33개의 이벤트를 나열하고 있습니다 (Anthropic hooks reference, 2026-09-24 읽기). 대부분의 사람들은 열 가지 복사 붙여넣기 예제 목록을 통해 후크를 접합니다: 매번 편집할 때마다 실행되는 linter, 데스크톱 ping, rm -rf에 대한 가드 등입니다. 그런 다음 누군가 후크를 사용해 토큰 사용량을 42% 또는 90% 줄였다는 글을 읽고 자신만의 후크도 도움이 되는지 아니면 방해가 되는지 궁금해합니다. 둘 다 가능합니다. UserPromptSubmit에서 stdout으로 출력하는 후크는 전송하는 모든 프롬프트마다 그 텍스트를 대화에 추가합니다. 실행되기 전에 도구 호출(tool call)을 재작성하는 후크는 3,000줄짜리 로그가 모델에 도달하는 것을 완전히 막을 수 있습니다. 차이점은 예제 목록에서 건너뛴 두 가지 세부 사항에 있습니다: 후크가 어떤 이벤트를 듣는지, 그리고 어떤 JSON 필드를 반환하는지입니다. 본 기사는 공식 레퍼런스만을 사용하여 토큰과 관련된 모든 이벤트를 무엇을 추가하거나 제거하는지 매핑하고, 후크가 조용히 아무것도 하지 않게 만드는 함정들을 나열합니다.
요약 (TL;DR)
- 네 가지 이벤트(
UserPromptSubmit, UserPromptExpansion, SessionStart, PostModelSwitch)는 일반 stdout을 Claude의 컨텍스트에 추가합니다. 다른 모든 이벤트에서는 성공적인 후크의 stdout이 디버그 로그로 전송되어 비용이 들지 않습니다.
additionalContext는 설계상 토큰 비용이 발생합니다: Claude Code가 이를 시스템 알림(system reminder)으로 감싸서 후크가 실행된 위치에 삽입하며, 그 이후에도 대화에 남아 있습니다.
- 두 개의 필드가 토큰을 제거합니다:
PreToolUse에서의 updatedInput(실행 전에 호출 재작성)과 PostToolUse에서의 updatedToolOutput(Claude가 읽기 전에 결과 대체).
- 주입되는 모든 문자열은 10,000자로 제한됩니다. 이를 초과하면 Claude는 파일 경로와 2,000자 미리보기를 받습니다.
- Exit 1은 차단하지 않으며, 시간 초과된
PreToolUse 후크는 호출을 통과시키고, 오타가 난 스크립트 경로는 아무것도 중지시키지 않고 게이트를 비활성화합니다.
비용 관점에서 본 후크란 무엇인가
후크(hook)란 Claude Code가 세션의 특정 지점에서 실행하는 핸들러입니다. 이 지점은 셸 명령어, HTTP 엔드포인트, 연결된 MCP 서버의 도구, LLM 프롬프트 또는 서브 에이전트일 수 있습니다 (참고). 해당 참고 자료는 이벤트들을 발생 빈도(cadence)별로 그룹화합니다. SessionStart와 SessionEnd는 세션당 한 번씩 발생하며, UserPromptSubmit, Stop, 그리고 StopFailure는 턴(turn)당 한 번씩 발생합니다. PreToolUse와 PostToolUse는 에이전트 루프 내에서 도구가 호출될 때마다 발생하므로, 에이전트가 실행하는 파일 읽기, 검색 및 명령어 실행 횟수만큼 카운트가 증가합니다.
이러한 발생 빈도가 비용 문제의 첫 번째 절반을 차지합니다. 세션당 한 번 발생하고 400자의 데이터를 주입하는 핸들러는 해당 문자 수만큼의 비용을 한 번 지불하며, 이후 요청마다 이 데이터가 재읽히면서 추가 비용이 발생합니다. PreToolUse에서 동일한 400자는 세션 내 모든 도구 결과 옆에 위치하게 됩니다. 대화에 들어가는 모든 내용은 후속 모델 요청 시 나머지 히스토리와 함께 다시 전송되므로, 프롬프트 캐싱이 에이전트에게 매우 중요한 이유입니다. 캐시된 재읽기는 신규 입력보다 저렴하지만, 결코 무료는 아닙니다.
두 번째 절반은 핸들러가 반환하는 내용물입니다. 후크는 토큰을 가지고 네 가지 작업을 수행할 수 있습니다:
- 아무것도 하지 않기(Nothing). 실행만 하고 0으로 종료하며, 표준 출력(stdout)은 디버그 로그로 전송됩니다. 대부분의 포맷팅, 알림 및 로깅 후크가 여기에 속하며, 토큰 비용이 전혀 발생하지 않습니다.
- 컨텍스트 추가하기(Add context): 해당 이벤트를 수용하는 네 가지 이벤트에 대해 일반 표준 출력(plain stdout)으로 제공하거나, JSON 형식의
additionalContext를 통해 제공할 수 있습니다.
- 존재하기 전에 컨텍스트 제거하기(Remove context before it exists): 도구의 인수를
updatedInput을 사용하여 재작성하거나, 그 결과를 updatedToolOutput으로 대체할 수 있습니다.
- 모델 호출하기(Call a model): 프롬프트 기반 및 에이전트 기반 후크는 후크 입력을 Claude로 전송하며, 이는 별도의 과금 요청이 됩니다.
나머지 부분에서는 이벤트들을 이 네 가지 버킷으로 분류합니다. 만약 세션의 토큰이 어디에 사용되는지 측정해 본 적이 없다면, 먼저 그렇게 하십시오. measuring agent token usage는 세션 트랜스크립트에서 사용량 필드를 읽는 방법을 보여줍니다.
토큰을 추가하는 후크 (The hooks that add tokens)
Claude Code의 일반 stdout에 대한 규칙은 간단합니다. exit 0 기준으로, "대부분의 이벤트에서 Claude Code는 stdout을 디버그 로그에 기록하며 트랜스크립트에 표시하지 않습니다. 예외는 UserPromptSubmit, UserPromptExpansion, SessionStart, 그리고 PostModelSwitch이며, 이 경우 Claude Code는 일반 텍스트 stdout을 Claude가 보고 행동할 수 있는 컨텍스트로 추가합니다" (reference, exit code 0).
따라서 SessionStart 후크에서의 echo는 작고 일회적인 추가입니다. UserPromptSubmit 후크에서의 동일한 echo는 모든 프롬프트마다 추가됩니다. 해당 참고 자료에는 일반 stdout이나 UserPromptSubmit의 additionalContext 둘 다 눈에 보이는 트랜스크립트 항목을 생성하지 않는다고 언급되어 있습니다. 각각은 후크 이름으로 시작하는 시스템 알림(system reminder)으로 주입됩니다. 사용자가 화면에서 볼 수 없는 텍스트 때문에 청구서가 늘어나는 것이 가장 흔한 방식이며, 이것이 바로 아무도 모르게 비용이 증가하는 원인입니다.
additionalContext는 구조화된 버전이며 더 많은 이벤트에서 작동합니다. PreToolUse와 PostToolUse의 경우, 이는 "툴 결과와 함께 Claude의 컨텍스트에 추가되는 문자열"입니다. SessionStart의 경우, 이는 "대화 시작 시점, 첫 번째 프롬프트 이전에" 추가됩니다. SubagentStart 및 PostModelSwitch의 경우, 이것이 후크가 할 수 있는 유일한 일입니다: 차단(blocking) 없이 컨텍스트를 제공하는 것입니다 (reference, decision control). 여러 후크가 동일한 이벤트에 대해 additionalContext를 반환할 경우, Claude는 모든 값을 받게 됩니다.
세 번째 경로는 덜 명확합니다. PostToolUse와 PostToolUseFailure에서 2를 반환(exiting 2)하면 "도구는 이미 실행되었습니다. stderr에 표시됩니다."라는 메시지가 Claude에게 전달됩니다. 참고 자료에서는 이 이벤트들로부터 경고를 표출하는 방법으로 이를 권장합니다. 모든 경고는 대화 내의 텍스트이므로, 각 편집마다 80줄의 발견 사항을 포함하여 2로 종료되는 linter hook은 편집당 80줄을 추가합니다.
Stop hook은 비용이 다르게 책정됩니다. Stop에서 2를 반환하면 "Claude가 중지하는 것을 방지하고 대화를 계속합니다."라는 의미입니다. 이는 전체 기록(full history)을 다시 전송하는 완전히 새로운 모델 턴(model turn)이 됩니다. 매 턴마다 실패하는 검사는 사용자가 개입할 때까지 세션을 지속시킵니다. 프롬프트 기반 변형에는 이를 위한 가드(guard)가 있습니다: 모델이 ok: false와 함께 impossible을 반환할 때, Claude Code는 이유를 다시 피드백하는 대신 턴을 종료하도록 허용합니다. Command hook에는 그러한 가드가 없으며, 루프의 경계 설정은 사용자의 책임입니다.
유지해야 할 패턴: 컨텍스트 주입(context injection)은 텍스트가 짧고, 모델의 다음 동작을 변경하며, 작업을 수행하는 데 필요한 가장 낮은 빈도(lowest cadence)로 발생할 때 적절합니다. SessionStart 시점에서의 브랜치 이름과 열린 이슈는 이 테스트를 통과합니다. 모든 UserPromptSubmit마다 2,000자 분량의 스타일 가이드는 CLAUDE.md에 속하며, 그곳에서는 적어도 캐시된 접두사(cached prefix)의 일부입니다.
토큰을 줄이는 후크들 (The hooks that cut tokens)
두 필드는 후크가 모델이 읽는 내용을 축소할 수 있게 해주며, 도구 호출(tool call)의 양 끝단에서 작동합니다.
PreToolUse의 updatedInput: 도구 실행 전에 도구의 입력 매개변수를 수정합니다. 전체 입력 객체를 대체하므로, 변경되지 않은 필드와 함께 수정된 필드를 포함해야 합니다. (참고 자료, PreToolUse 결정 제어). 대부분의 토큰 절약 후크가 작동하는 곳입니다. npm test에 대한 Bash 호출은 실패만 유지하도록 필터링을 거치도록 재작성될 수 있습니다. 5,000줄 분량의 파일을 읽는 작업은 offset과 limit으로 범위를 좁힐 수 있으며, 경로 없이 실행되는 Grep은 src/로 범위가 제한될 수 있습니다. 모델은 항상 잘라낸 결과만 읽으며, 추가적인 내용은 컨텍스트에 들어가지 않습니다. 참고 자료에서 두 가지 세부 사항이 중요합니다. 권한 규칙은 'Claude가 보낸 입력이 아닌, 후크가 반환하는 입력을 기준으로' 평가되므로, 재작성을 통해 거부(deny) 규칙을 우회할 수 없습니다. 그리고 대체는 전체 객체 단위로 이루어집니다: 필드를 누락하면 도구는 해당 필드 없이 실행됩니다.
PostToolUse의 updatedToolOutput: 도구 출력을 Claude에게 전송하기 전에 제공된 값으로 대체합니다. 이 값은 도구의 출력 형태와 일치해야 합니다. 참고 자료에서는 이제 이전 버전인 updatedMCPToolOutput(MCP 도구에만 작동)보다 이것을 권장합니다. 결과가 얼마나 클지 미리 알 수 없는 경우에 사용해야 하는 필드입니다: 호출을 실행한 다음, 빌드 로그의 오류 라인을 유지하거나, 진행 표시줄(progress bars)을 제거하거나, JSON 응답을 모델이 필요한 키로 자를 수 있습니다. 참고 자료 예제는 Bash 결과의 stdout을 수정된 문자열로 대체하여 형태를 보여줍니다.
PostToolUse에서 decision: "block"이 무엇을 하고 무엇을 하지 않는지 주목하십시오: 이는 도구 결과 옆에 reason을 추가합니다. Claude는 여전히 원래 출력을 보게 되며, 이를 대체하려면 updatedToolOutput을 사용해야 합니다. 거대한 출력을 숨기기 위해 설계된 블로킹(blocking) PostToolUse 후크는 텍스트를 추가할 뿐, 아무것도 제거하지 않습니다.
이 접근 방식에 대해 발표된 결과는 단일 팀 벤치마크(single-team benchmarks)이므로, 그 수치를 크기 순서(orders of magnitude)로 이해해야 합니다. SitePoint는 2026년 8월 20일에 후크 기반 프레임워크인 Graft가 저자들의 내부 SWE-bench Verified 실행에서 작업당 평균 토큰 사용량을 8,070개에서 4,650개로 줄여 42% 감소를 기록했다고 보고했습니다(SitePoint). Spotify Engineering은 2026년 9월에 "Spotify의 Portal이 나의 Claude Code 토큰 사용량을 90% 줄였다"라는 제목의 엔지니어 계정을 게시했는데, 이 글은 코딩 에이전트가 수행하는 대부분의 작업이 하나의 메서드에 대한 질문에 답하기 위해 다섯 개의 파일을 읽는 것과 같은 I/O(입출력) 과정이라고 주장합니다(Spotify Engineering). 이 두 사례 모두 사용자의 저장소에 대한 통제된 테스트는 아닙니다. 두 경우의 근간이 되는 메커니즘은 위에서 설명한 것이며, 일반적인 기술 자체에 별도의 페이지가 있습니다: 명령 로그를 위한 출력 필터링.
10,000자 제한 (The 10,000-character ceiling)
후크가 주입하는 모든 문자열에는 하드 리미트(hard limit)가 있습니다. 참고 자료에 따르면: "후크의 additionalContext, systemMessage, 그리고 initialUserMessage 문자열과 그 일반적인 stdout은 10,000자로 제한됩니다." 각 JSON 필드는 개별적으로 측정되며, 일반적인 stdout은 전체로 측정되고, 하나의 이벤트에서 여러 후크는 각각 독립적으로 측정됩니다(참고 자료, JSON 출력).
제한을 초과할 경우, Claude Code는 잘라내지 않습니다(truncate). 대신 "출력을 세션 디렉터리의 파일로 저장하고, 해당 파일 경로와 최대 처음 2,000자까지의 미리보기로 대체합니다." Claude는 그 파일을 읽을 수 있지만, Claude Code가 그렇게 요청하지는 않습니다. 대용량의 유효한 Bash 결과도 같은 방식으로 처리됩니다. Bash 제한과 달리, 후크 제한은 "높일 수 있는 설정이나 환경 변수가 없습니다."
비용에 대한 두 가지 영향:
- 캡(cap)은 문자열당 천장 제한이며, 매 프롬프트마다 9,999자의 내용을 전송하는 것을 막는 것은 없습니다. 이는 무한정 실행되는 후크(runaway hooks)를 방지하며, 이 제한 내에 머무르는 후크라도 컨텍스트에서 가장 큰 단일 항목이 될 수 있습니다.
- 캡을 초과하면 상황이 더 나빠질 수 있습니다. 만약 모델이 전체 텍스트가 필요하다고 판단할 경우, 도구 호출(tool call)을 통해 파일을 읽게 되며, 이 경우 미리 보기 내용, 도구 호출 자체, 그리고 전체 콘텐츠에 대해 비용을 지불하게 됩니다.
후크가 정기적으로 수백 자 이상의 내용을 생성하는 경우, 더 나은 설계 방식은 세부 정보를 직접 파일로 작성한 다음, 해당 파일로 연결되는 한 줄의 내용을 주입(inject)하는 것입니다.
모델을 호출하는 후크 (Hooks that call a model)
프롬프트 기반 후크(type: "prompt")는 “후크 입력과 프롬프트를 Claude 모델에 전송합니다. 기본값은 Haiku입니다.”라고 작동하며, model 필드를 통해 이를 재정의할 수 있습니다(reference, prompt-based hooks). 에이전트 기반 후크(Agent-based hooks)는 더 나아가 하위 에이전트를 실행하여 결정을 내리기 전에 도구를 사용할 수 있게 합니다. 이 두 가지 모두 메인 세션 비용 외에 요청으로 청구됩니다.
이는 높은 빈도의 이벤트에 대한 계산을 변경시킵니다. Stop 후크가 Stop 시점에 적용되면 턴(turn)당 한 번 실행되므로 일반적으로 허용 가능합니다. 반면, PreToolUse 후크는 도구 호출이 발생할 때마다 모델 요청을 실행하며, 각 요청에는 전체 도구 인수를 포함한 후크 입력이 담깁니다. 정규 표현식이나 jq로 처리할 수 있는 확인 작업의 경우, 커맨드 후크(command hook)는 토큰 비용이 들지 않고 밀리초 단위로 답변합니다.
판단(judgment)이 핵심인 경우에 프롬프트 및 에이전트 후크를 사용해야 합니다. 예를 들어, Claude가 멈추기 전에 작업이 정말 완료되었는지 판단하거나, diff가 규칙이 금지하는 무언가를 건드렸는지 여부를 결정할 때 유용합니다. 추가 요청을 측정하지 않았다면 도구 호출(per-tool-call) 경로에는 이 후크들을 사용하지 않는 것이 좋습니다. Agentic coding costs도 동일한 논리를 따릅니다: 새로운 모델 컨텍스트가 시작될 때마다 자체 오버헤드가 발생합니다.
PreModelSwitch: 후크에서 처음으로 네이티브 비용 신호를 제공하는 기능