10. Hook

규칙 파일과 훅의 강제력 차이 — CLAUDE.md 규칙은 모델이 읽고 스스로 지키는 반면, 훅은 시스템이 직접 실행해 어길 수 없다.

1 – Hook의 개념과 설정 방법

여기서는 Hook이 무엇인지 알아보고, 설정 파일에 Hook을 적는 방법을 다룹니다. Claude Code를 터미널에서 실행해 본 적이 있고 프로젝트 폴더에 CLAUDE.md 파일을 두어 본 사람을 독자로 잡았습니다.

Hook(훅)은 Claude Code가 작업하는 도중 정해진 순간마다 자동으로 실행되는 명령입니다. 모델이 실행할지 말지 판단하지 않고 시스템이 직접 실행하므로, 그 순간이 오면 예외 없이 실행됩니다.

1.1 – Hook과 CLAUDE.md 규칙의 차이

Hook과 CLAUDE.md는 둘 다 Claude Code의 행동을 정하는 장치입니다. 다만 규칙이 지켜지는 방식이 다릅니다.

CLAUDE.md는 세션을 시작할 때 Claude Code가 가장 먼저 읽는 규칙 파일입니다. 규칙을 문장으로 적어 두면 모델이 그 문장을 읽고 스스로 지킵니다. 읽고 지키는 구조이므로 모델이 그 문장을 지나치면 규칙도 함께 지나갑니다.

두 장치의 차이를 길에 빗대면 이렇습니다. CLAUDE.md 규칙은 복도에 붙인 “뛰지 마세요” 안내문이고, Hook은 복도에 설치한 과속방지턱입니다. 안내문은 읽어야 효과가 있지만, 과속방지턱은 읽지 않아도 작동합니다.

구분CLAUDE.md 규칙Hook
작동 방식모델이 읽고 스스로 지킵니다시스템이 실행합니다
어길 가능성있습니다없습니다
판단이 필요한 일알맞습니다(예: “코드를 간결하게”)알맞지 않습니다
무조건 지켜야 할 일알맞지 않습니다알맞습니다(예: “이 파일 수정 금지”)

그래서 두 장치는 아래 순서로 씁니다. 같은 실수가 되풀이될수록 더 강한 쪽으로 옮겨 갑니다.

  1. 1~2회 나온 실수는 기록만 해 둡니다.
  2. 3회 이상 되풀이되면 CLAUDE.md에 규칙 문장으로 적습니다.
  3. 규칙으로 적어 둔 뒤에도 다시 어기면 그 규칙을 Hook으로 옮깁니다.

1.2 – 실행 순간 고르기

이벤트는 Hook을 실행할 순간을 가리키는 이름입니다. Hook을 적을 때 이 이름을 먼저 골라, 언제 명령이 실행될지를 정합니다.

자주 쓰는 이벤트는 아래 네 가지입니다.

이벤트설명
PostToolUse 도구 사용이 성공한 직후입니다. 코드 정렬 같은 뒷정리에 씁니다
UserPromptSubmit사용자가 메시지를 보낸 직후, 모델이 그 메시지를 읽기 전입니다. 메시지 검사와 기록에 씁니다.
Stop 모델이 답변을 끝냈을 때입니다. 작업이 끝났는지 검증하는 데 씁니다
SessionStart 세션을 시작할 때입니다. 프로젝트 정보를 자동으로 알려 주는 데 씁니다

이 밖에도 도구 실패 직후(PostToolUseFailure), 권한 요청 시(PermissionRequest), 하위 에이전트 시작·종료(SubagentStart·SubagentStop) 같은 이벤트가 있습니다. 이벤트는 30종 가까이 되며, 전체 목록은 아래 출처에 적은 Hook 상세 레퍼런스에 있습니다.

1.3 – 설정 파일 자리 고르기

Hook은 JSON 설정 파일에 적습니다. 어느 파일에 적느냐에 따라 그 Hook이 적용되는 범위가 달라지므로, 적기 전에 자리를 먼저 고릅니다.

파일 위치적용 범위팀 공유
~/.claude/settings.json내 컴퓨터의 모든 프로젝트안 됩니다
프로젝트폴더/.claude/settings.json이 프로젝트만됩니다(git으로 공유)
프로젝트폴더/.claude/settings.local.json이 프로젝트, 나만안 됩니다

여럿이 함께 쓰는 프로젝트라면 두 번째 자리인 .claude/settings.json을 고릅니다. 이 파일을 git에 올리면 내려받은 사람 모두에게 같은 Hook이 적용되기 때문입니다.

1.4 – 설정 파일의 3단 구조

설정은 “언제 → 어떤 도구에 → 무엇을 실행”의 3단으로 적습니다. 아래 코드는 세 단이 모두 들어간 설정 한 벌입니다.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/check.sh"
          }
        ]
      }
    ]
  }
}

위 설정은 아래 세 단으로 읽습니다.

  1. 1단 "PreToolUse" — 언제: 도구를 사용하기 직전에 실행합니다.
  2. 2단 "matcher": "Bash" — 어떤 도구에: Bash(터미널 명령) 도구를 쓸 때만 실행합니다.
  3. 3단 "command": "...check.sh" — 무엇을: check.sh 스크립트를 실행합니다.

${CLAUDE_PROJECT_DIR}는 현재 프로젝트 폴더 경로로 바뀌는 값입니다. 사람마다 폴더 경로가 다르므로, 경로를 직접 적는 대신 이 값을 씁니다.

참고: 명령 실행 말고 다른 방식으로 도는 Hook

type에는 셸 명령을 실행하는 command 말고도 아래 네 가지가 있습니다. 처음 배우는 단계에서는 command만으로 충분하며, 이런 것도 있다는 정도로 알아 둡니다.

  1. prompt — 판단이 필요한 검사를 작은 모델에게 예·아니오로 묻습니다.
  2. agent — 복잡한 검증을 하위 에이전트에게 맡깁니다.
  3. http — 외부 서버로 JSON을 보냅니다.
  4. mcp_tool — 연결된 MCP 서버의 도구를 호출합니다.

1.5 – matcher로 적용 대상 좁히기

matcher는 이벤트가 일어난 대상 가운데 어떤 것에만 Hook을 걸지 고르는 값입니다. 2단에 적으며, 아래 네 가지 형태로 씁니다.

  1. "Bash" — Bash 도구에만 겁니다.
  2. "Edit|Write" — Edit 또는 Write에 겁니다. 막대 기호 |는 “또는”이라는 뜻입니다.
  3. ""(빈 문자열) 또는 생략 — 모든 도구에 겁니다.
  4. "mcp__서버명__.*" — 특정 MCP 서버의 모든 도구에 겁니다. 정규식을 쓸 수 있습니다.

1.6 – 스크립트가 정보를 받고 결과를 알리는 방법

Hook 스크립트는 두 방향으로 정보를 주고받습니다. 들어올 때는 표준 입력으로 JSON을 받고, 나갈 때는 종료 코드로 결과를 알립니다.

Hook이 실행되면 Claude Code가 지금 무슨 일이 벌어지는지를 JSON 형태로 스크립트의 표준 입력(stdin)에 넣어 줍니다. PreToolUse 이벤트라면 아래 내용이 들어옵니다.

{
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": { "command": "rm -rf build" },
  "cwd": "/내/프로젝트/경로"
}

스크립트 안에서는 jq 명령으로 필요한 값만 꺼냅니다. jq -r '.tool_input.command'라고 쓰면 실행하려는 명령어 문자열만 뽑아냅니다.

종료 코드(exit code)는 스크립트가 끝날 때 남기는 숫자입니다. Claude Code는 이 숫자를 보고 작업을 그대로 진행할지 막을지 정합니다.

종료 코드어떻게 됩니까
exit 0작업이 그대로 진행됩니다.
exit 2작업이 막히고, 화면에 출력한 내용(stderr)이 이유로 모델에게 전달됩니다.
그 밖의 값(1 등)경고만 표시되고 작업은 그대로 진행됩니다.

즉 “검사해서 문제가 있으면 exit 2, 없으면 exit 0“이 Hook 스크립트의 기본 형태입니다.

실습 코칭 프롬프트

여기까지 읽고 내 프로젝트에 어떤 Hook을 걸지 정하다 막히면, 아래 프롬프트를 대화형 AI에 붙여 넣어 한 단계씩 도움을 받습니다.

실습에 쓰던 대화창이 아니라 새 대화창에 붙여 넣습니다. 쓰던 대화창에는 앞서 시킨 다른 역할의 응답이 남아 있어, 그 위에 얹으면 답이 뒤섞입니다.

아래는 Claude Code의 Hook을 처음 설정하려는 사람의 요청입니다. 답을 통째로 주지 말고 한 번에 한 단계씩만 안내해 주세요.

되풀이되는 문제: {되풀이되는 실수}
지금 쓰는 설정 파일 자리: {설정 파일 경로}
막힌 지점: {막힌 지점}

지켜 주세요.
1. 완성된 설정 JSON과 스크립트를 한꺼번에 내놓지 않습니다.
2. 먼저 이 문제가 CLAUDE.md 규칙으로 충분한지, Hook으로 옮겨야 하는지를 되물어 판단하게 해 주세요.
3. Hook이 맞다면 이벤트 이름 → matcher → 실행할 명령 순서로 하나씩만 물어봐 주세요.
4. 제가 답한 뒤에 그 답이 맞는지 확인 질문을 던져 주세요.
5. 이벤트를 잘못 고르거나 matcher를 너무 넓게 잡는 실수가 보이면 그 자리에서 짚어 주세요.
아래는 Claude Code의 Hook을 처음 설정하려는 사람의 요청입니다. 답을 통째로 주지 말고 한 번에 한 단계씩만 안내해 주세요.

되풀이되는 문제: 확인 없이 파일을 지웁니다
지금 쓰는 설정 파일 자리: .claude/settings.json
막힌 지점: 이벤트를 PreToolUse로 잡아야 하는지 PostToolUse로 잡아야 하는지 모르겠습니다

지켜 주세요.
1. 완성된 설정 JSON과 스크립트를 한꺼번에 내놓지 않습니다.
2. 먼저 이 문제가 CLAUDE.md 규칙으로 충분한지, Hook으로 옮겨야 하는지를 되물어 판단하게 해 주세요.
3. Hook이 맞다면 이벤트 이름 → matcher → 실행할 명령 순서로 하나씩만 물어봐 주세요.
4. 제가 답한 뒤에 그 답이 맞는지 확인 질문을 던져 주세요.
5. 이벤트를 잘못 고르거나 matcher를 너무 넓게 잡는 실수가 보이면 그 자리에서 짚어 주세요.

빈칸에는 아래 값을 채웁니다.

  1. {되풀이되는 실수} — 3회 이상 되풀이된 문제를 한 문장으로 적습니다. 예: “확인 없이 파일을 지웁니다”.
  2. {설정 파일 경로} — 1.3에서 고른 자리를 적습니다. 예: .claude/settings.json.
  3. {막힌 지점} — 지금 판단이 서지 않는 대목을 적습니다. 예: “이벤트를 무엇으로 잡아야 할지 모르겠습니다”.

AI가 완성된 설정을 통째로 내놓으면 “설명 말고 다음 한 단계만 알려 주세요”라고 되묻습니다. 자기 상황을 적을 때는 쓰는 운영체제와 프로젝트 폴더 구조를 빠뜨리기 쉬우니 함께 적습니다.

2 – Hook 만들어 보기

앞에서 설정 파일의 3단 구조와 종료 코드를 익혔으니, 여기서는 그 구조로 Hook을 직접 만들어 봅니다. 아래 예제 세 가지는 모두 .claude/settings.json.claude/hooks/ 폴더를 씁니다.

2.1 – 위험한 삭제 명령 차단하기

“확인 없이 파일을 지운다”는 문제를 Hook으로 막습니다. 도구를 쓰기 직전에 검사해야 하므로 PreToolUse 이벤트를 고릅니다.

먼저 .claude/settings.json 파일을 열고 아래 내용을 적습니다.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh"
          }
        ]
      }
    ]
  }
}

다음으로 .claude/hooks/block-rm.sh 파일을 새로 만들고 아래 내용을 적습니다.

#!/bin/bash
CMD=$(jq -r '.tool_input.command')

if echo "$CMD" | grep -qE '\brm\b'; then
  echo "rm 명령은 금지되어 있습니다. 삭제 대신 _to_delete 폴더로 이동하세요." >&2
  exit 2
fi
exit 0

마지막으로 터미널에서 chmod +x .claude/hooks/block-rm.sh를 실행해 실행 권한을 줍니다.

이 스크립트는 실행하려는 명령어에 rm이 들어 있으면 exit 2로 끝납니다. 종료 코드가 2이므로 명령은 실행되지 않고, >&2로 내보낸 문장이 차단 이유로 모델에게 전달됩니다.

실행 권한을 주지 않으면 Hook이 실행되지 않고 넘어갑니다. 차단 메시지가 나오지 않으면 chmod +x를 실행했는지 먼저 확인합니다.

2.2 – 파일을 고친 뒤 코드 자동 정렬하기

“코드 정렬을 자주 빼먹는다”는 문제를 Hook으로 메웁니다. 파일을 고친 다음에 정렬해야 하므로 PostToolUse 이벤트를 고릅니다.

먼저 .claude/settings.json에 아래 내용을 적습니다.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/format.sh"
          }
        ]
      }
    ]
  }
}

다음으로 .claude/hooks/format.sh 파일을 만들고 아래 내용을 적습니다.

#!/bin/bash
FILE=$(jq -r '.tool_input.file_path')

case "$FILE" in
  *.js|*.jsx|*.ts|*.tsx|*.css)
    npx prettier --write "$FILE"
    ;;
esac
exit 0

matcher"Edit|Write"로 적었으므로 파일을 고치거나 새로 쓸 때만 실행됩니다. case 문은 확장자가 목록에 있는 파일만 골라 정렬하고, 그 밖의 파일은 그냥 지나갑니다. 마지막이 exit 0이므로 작업은 그대로 이어집니다.

2.3 – 도구 사용 기록 남기기

어떤 도구를 언제 썼는지 파일로 남깁니다. 나중에 실수의 원인을 되짚을 때 근거 자료가 됩니다.

먼저 .claude/settings.json에 아래 내용을 적습니다.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/log.sh"
          }
        ]
      }
    ]
  }
}

다음으로 .claude/hooks/log.sh 파일을 만들고 아래 내용을 적습니다.

#!/bin/bash
TOOL=$(jq -r '.tool_name')
NOW=$(date "+%Y-%m-%d %H:%M:%S")
echo "$NOW$TOOL" >> "${CLAUDE_PROJECT_DIR}/.claude/tool-log.txt"
exit 0

matcher를 빈 문자열로 두었으므로 모든 도구가 기록 대상이 됩니다. 도구를 쓸 때마다 .claude/tool-log.txt 파일에 시각과 도구 이름이 한 줄씩 쌓입니다.

2.4 – 등록 상태 확인하기

만든 Hook이 실제로 등록됐는지, 의도대로 도는지 확인합니다.

  1. Claude Code 안에서 /hooks를 입력합니다. 등록된 Hook 목록과 각 Hook이 어느 설정 파일에서 왔는지가 표시됩니다.
  2. 설정 파일을 고쳤다면 그대로 둡니다. 고친 내용은 자동으로 다시 읽힙니다.
  3. 일부러 그 상황을 만들어 봅니다. 2.1을 만들었다면 “build 폴더 지워 줘”라고 시켜서 차단 메시지가 나오는지 확인합니다.

목록에 Hook이 보이고 차단 메시지가 화면에 나타나면, 지금까지 한 작업이 제대로 된 것입니다.

실습 코칭 프롬프트

만든 Hook이 돌지 않을 때 아래 프롬프트를 대화형 AI에 붙여 넣어 원인을 한 단계씩 좁혀 갑니다.

실습에 쓰던 대화창이 아니라 새 대화창에 붙여 넣습니다. 쓰던 대화창에는 앞서 시킨 다른 역할의 응답이 남아 있어, 그 위에 얹으면 답이 뒤섞입니다.

아래는 Claude Code Hook이 의도대로 돌지 않아 원인을 찾으려는 요청입니다. 답을 통째로 주지 말고 한 번에 한 단계씩만 안내해 주세요.

만들려던 Hook의 목적: {Hook의 목적}
지금까지 한 것: {지금까지 한 것}
실제로 나타난 결과나 에러 메시지: {나타난 결과}

지켜 주세요.
1. 고친 설정 파일 전문과 스크립트 전문을 한꺼번에 내놓지 않습니다.
2. 먼저 어디까지는 제대로 됐는지 확인할 수 있는 점검 한 가지를 알려 주고, 결과를 물어봐 주세요.
3. 원인이 좁혀지면 고칠 곳 한 군데만 알려 주세요.
4. 제가 고친 뒤에 무엇을 확인해야 하는지 질문으로 되짚어 주세요.
5. 실행 권한을 주지 않은 것, 파일 경로를 잘못 적은 것, 종료 코드를 2가 아닌 값으로 둔 것처럼 자주 나오는 실수가 보이면 짚어 주세요.
아래는 Claude Code Hook이 의도대로 돌지 않아 원인을 찾으려는 요청입니다. 답을 통째로 주지 말고 한 번에 한 단계씩만 안내해 주세요.

만들려던 Hook의 목적: rm 명령을 실행하기 전에 막고 싶습니다
지금까지 한 것: .claude/settings.json에 PreToolUse 설정을 적고 .claude/hooks/block-rm.sh 파일을 만들었습니다
실제로 나타난 결과나 에러 메시지: 차단 메시지가 나오지 않고 명령이 그대로 실행됩니다

지켜 주세요.
1. 고친 설정 파일 전문과 스크립트 전문을 한꺼번에 내놓지 않습니다.
2. 먼저 어디까지는 제대로 됐는지 확인할 수 있는 점검 한 가지를 알려 주고, 결과를 물어봐 주세요.
3. 원인이 좁혀지면 고칠 곳 한 군데만 알려 주세요.
4. 제가 고친 뒤에 무엇을 확인해야 하는지 질문으로 되짚어 주세요.
5. 실행 권한을 주지 않은 것, 파일 경로를 잘못 적은 것, 종료 코드를 2가 아닌 값으로 둔 것처럼 자주 나오는 실수가 보이면 짚어 주세요.

빈칸에는 아래 값을 채웁니다.

  1. {Hook의 목적} — 무엇을 막거나 자동으로 하려 했는지 한 문장으로 적습니다.
  2. {지금까지 한 것} — 만든 파일 이름과 적은 내용을 적습니다. 예: .claude/hooks/block-rm.sh를 만들고 chmod +x를 실행했습니다.
  3. {나타난 결과} — 화면에 실제로 나온 문장을 그대로 옮깁니다. 아무 일도 일어나지 않았다면 그 사실을 적습니다.

AI가 고친 파일 전문을 통째로 내놓으면 “전문 말고 어디 한 줄을 고쳐야 하는지만 알려 주세요”라고 되묻습니다. 자기 상황을 적을 때는 /hooks 목록에 그 Hook이 보이는지 여부를 빠뜨리기 쉬우니 함께 적습니다.

출처

  1. Claude Code 공식 Hook 안내: https://code.claude.com/docs/en/hooks-guide
  2. Claude Code Hook 상세 레퍼런스: https://code.claude.com/docs/en/hooks

댓글 남기기