이 페이지는 아직 번역되지 않았습니다 — 영어 버전을 표시합니다.
Sign in to save your progressYou can keep reading without an account, but completed lessons won't be saved.
Sign in

Hooks

Here's the problem with telling Claude to do something in a CLAUDE.md file: it's a request, not a guarantee. You can write "always format after editing" and Claude will usually listen. Usually. But on a long run you're not watching, "usually" isn't good enough. A hook fixes that. A hook is deterministic code that runs at a fixed point in the loop, so it can guarantee behavior instead of hoping for it. It turns a rule from "Claude usually listens" into "Claude can't skip it."

That's the whole pitch. Now let's look at how it actually works.

한국어 대본
  • 00:00훅은 루프의 고정된 지점에서 실행되는 결정적 코드이므로, 훨씬 더 많은 것을 보장할 수 있습니다.
  • 00:08지켜보지 않는 실행에서 포맷팅 이상을 보장합니다. 이는 Claude가 보통 따르는 규칙을
  • 00:13Claude가 건너뛸 수 없는 규칙으로 바꿉니다. Claude Code는 약 30개의 훅 이벤트를 실행합니다. 주로 사용하게 될 몇 가지는
  • 00:19도구 사용 전입니다. 이는 도구 호출 전에 실행되는 강제 실행의 기본 요소이며, 도구 사용 후는
  • 00:24성공적인 도구 호출 후에 실행됩니다. 이곳은 일반적으로 자동 포맷팅이나 자동 린트가
  • 00:29실행되는 곳입니다. Stop은 Claude가 응답을 끝내려 할 때 실행됩니다. 조건이 충족되면 Refuse가 실행됩니다. Subagent
  • 00:35stop은 동일한 신호이지만 Subagent가 끝날 때를 위한 것입니다. Pre-compact와 post-compact는 압축 전과 후에 실행됩니다.
  • 00:40압축 후에 실행됩니다. 압축 후 컨텍스트를 다시 주입하려면 compact 매처와 함께 session start를 사용하세요.
  • 00:45post-compact를 사용하지 마세요. Instructions loaded는 Claude.md 또는 규칙 파일이 로드될 때 실행됩니다. 무엇이 컨텍스트에 들어왔는지
  • 00:50점검하세요. Session start는 환경을 준비합니다. 소스 시작을 확인하여 새로 시작할 때만 실행되도록 하세요.
  • 00:56새로 시작할 때만 실행됩니다. 도구 사용 전의 경우 JSON을 반환하고 권한 결정 필드와 함께 0으로 종료하세요.
  • 01:02allow, deny, ask 중 하나입니다. 네 번째 값인 defer도 있지만, 이는 비대화형 -p 실행에만 적용됩니다.
  • 01:08호출 프로세스가 도구를 일시 중지했다가 나중에 다시 시작하는 경우이므로, 이 값을 사용할 일은 거의 없습니다.
  • 01:13또한 차단하지 않고 도구 호출을 수정하기 위해 업데이트된 입력을 반환할 수 있습니다. 이는 유용할 수 있습니다.
  • 01:17bash 명령에서 비밀을 삭제하는 데 유용합니다. 한 가지 주의할 점은 업데이트된 입력이 전체를 대체한다는 것입니다.
  • 01:22입력 객체를 대체한다는 것입니다. 따라서 변경하지 않는 필드는 그대로 다시 출력하세요. JSON을 반환하지 않는 훅의 경우
  • 01:28종료 코드도 작동합니다. 여기 중요한 세 가지 숫자가 있습니다. 0은 성공입니다. 따라서 표준
  • 01:34출력이 JSON이면 Claude가 이를 파싱합니다. 일반 텍스트는 대부분의 이벤트에서 무시되지만, session start에서는
  • 01:39user prompt submit과 user prompt expansion에서는 텍스트가 컨텍스트에 추가됩니다. 이것이 바로
  • 01:44상태 보존기가 작동하게 하는 것입니다. 2는 차단 오류입니다. 따라서 표준 오류는 Claude에게
  • 01:50컨텍스트로 전달됩니다. 이것은 거의 모든 곳에서 차단 종료 코드입니다. 유일한 예외는 WorkTree
  • 01:55create가 0이 아닌 모든 코드를 중단시킵니다. 주의하세요. exit 1은 오류처럼 보이지만 실제로는
  • 02:00차단하지 않으므로 Claude는 어쨌든 명령을 실행합니다. 그리고 그 외의 모든 것은 비차단이며 기록됩니다.
  • 02:05Exit 2는 stop을 차단할 수 있습니다. 이것은 '아니요, 아직 끝나지 않았습니다'라고 말하는 방법입니다. 도구 사용 후는
  • 02:10도구가 이미 실행된 후이므로 호출을 중지하기에는 너무 늦었지만, 여전히 텍스트를 Claude에게 다시 전달할 수 있습니다.
  • 02:15알림, 세션 시작, 파일 변경과 같은 일부 이벤트는 차단을 무시하고 표준 오류를 표시한 다음 계속 진행합니다.
  • 02:23차단하는 대신 내용을 수정하는 사전 도구 사용 가드레일.
  • 02:26매처는 감시할 도구(예: bash)를 선택하고, 선택적 if 절은 특정 명령으로 범위를 좁힙니다.
  • 02:32따라서 deny를 반환하면 위험한 호출이 중단됩니다.
  • 02:35덜 알려진 방법은 업데이트된 입력을 반환하여 호출을 다시 작성하는 것으로, 명령에서 비밀을 제거하고도 계속 실행되게 하는 방법입니다.
  • 02:42Claude가 긴 대화를 압축하면 많은 세부 정보가 사라지지만, compact 매처가 있는 세션 시작 훅은 압축 직후 실행되어 작업 중이던 파일에 대한 간단한 요약을 출력합니다.
  • 02:55그 요약은 컨텍스트로 다시 들어가므로 Claude가 처음부터 시작하는 대신 중단했던 지점에서 이어갑니다.
  • 03:01훅은 Claude가 보통 따르는 규칙을 항상 따르는 규칙으로 바꿉니다.
  • 03:05자동 서식 지정을 넘어서고, 사전 도구 사용으로 도구를 보호하고, stop으로 턴을 제어하고, compact 전반에 걸쳐 상태를 유지하세요.
  • 03:14그 설정은 지켜보지 않던 실행에서 무언가를 처음 잡아낼 때 효과를 발휘합니다.
Watch on YouTube

The hook events

Claude Code fires around 30 hook events over the course of a session. You don't need to know all of them. There's a small handful you'll reach for again and again, and they line up with points in the agentic loop where you'd want to step in.

Here's how they sit in the loop. A session starts, prompts come in, tools get called, and the turn eventually ends. Each of those moments has a hook you can hang code on.

The ones worth knowing:

  • PreToolUse fires before a tool call. This is your enforcement primitive. It's the one that can stop something before it happens.
  • PostToolUse fires after a successful tool call. This is usually where auto-formatting or an auto-lint goes.
  • Stop fires when Claude wants to end its turn. You can refuse and say "no, you're not done yet" if some condition isn't met. There's a matching SubagentStop for when a sub-agent finishes.
  • PreCompact and PostCompact fire before and after compaction.
  • InstructionsLoaded fires when a CLAUDE.md or rule file loads. Handy for auditing what actually made it into context.
  • SessionStart fires at the start and primes the environment. Use the startup source if you only want it on fresh starts.

One thing that trips people up: to re-inject context after compaction, don't use PostCompact. Use SessionStart with the compact matcher. That's the one that actually gets its output back into the conversation.

PreToolUse: returning a decision as JSON

PreToolUse is where the real power is, because it can block a tool call before it runs. The way you talk back to Claude is by printing JSON and exiting zero. The key field is permissionDecision, and it takes one of three values:

  • allow — let the call through
  • deny — stop the call
  • ask — hand it back to the user to decide

There's technically a fourth value, defer, but it only applies to non-interactive -p runs where a calling process pauses the tool and resumes it later. You'll rarely reach for it.

The shape looks like this:

json
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "...",
    "updatedInput": {
      "command": "..."
    }
  }
}

Notice updatedInput. Instead of blocking a call, you can rewrite it. That's how you'd redact a secret out of a bash command and still let it run. One catch: updatedInput replaces the whole input object, so you have to echo back the fields you aren't changing, or you'll lose them.

Exit codes, for hooks that don't return JSON

Not every hook needs to speak JSON. For simpler hooks, exit codes do the job. There are three numbers that matter.

  • 0 is success. If standard out is JSON, Claude parses it. Plain text is ignored on most events, but on SessionStart, UserPromptSubmit, and UserPromptExpansion, plain text gets added to context. That's exactly what makes a state-preserver hook work.
  • 2 is a blocking error. Standard error gets fed back to Claude as context. This is the blocking exit code almost everywhere.
  • Anything else is non-blocking. Standard error gets logged, and Claude carries on.

The one that catches people out is exit code 1. It feels like an error, but it does not block. Claude runs the command anyway. So if you meant to stop something, exit 2, not 1.

A couple more wrinkles. Exit 2 can even block Stop, which is how you tell Claude it's not done. But PostToolUse fires after the tool already ran, so blocking there is too late to stop the call, though it can still feed text back to Claude. And a few events ignore blocking entirely, like Notification and SessionStart. They'll show your standard error and carry on regardless.

A real guardrail: redact instead of block

Let's tie it together with something practical. Say you want a PreToolUse guardrail on the Bash tool. The matcher picks the tool to watch, and an optional if clause can narrow it to a specific command.

The obvious move is to return deny and stop a dangerous call. That's good. But the lesser-known and more interesting move is to return updatedInput to rewrite the call. That's how you strip a secret out of a command and still let it run, instead of just refusing.

Here's what that looks like in practice. Claude is asked to run a command that includes a live-looking secret. The hook intercepts it, spots the sk_live_ pattern, and swaps it for a placeholder before the command ever executes.

The command still ran. The work still got done. But the secret never made it through. That's the difference between blocking and redacting, and it's the kind of thing a hook can enforce every single time.

Preserving state across a compact

One more pattern worth setting up. When Claude compacts a long conversation, it drops a lot of detail. A SessionStart hook with the compact matcher runs right after compaction. Have it print a short summary of the files you've been working on. That summary goes back into context, so Claude picks up where it left off instead of starting cold.

Wrapping up

Hooks turn a rule Claude usually follows into one it always follows. Reach past auto-formatting: guard tools with PreToolUse, gate the turn with Stop, and preserve state across a compact. The setup takes a little effort up front, but it pays back the first time it catches something on a run you weren't even watching.