본문 바로가기
Mechanic: IT 인터넷/Mechanic, M-Tech

Claude Code Hooks 가이드 - 파일 저장할 때 자동으로 명령어 실행하는 방법

by M-LOG : 엠로그 2026. 2. 23.
반응형

Claude Code Hooks는 클로드 코드가 특정 동작을 할 때 내가 지정한 쉘 명령어를 자동으로 실행해주는 기능이다. 파일 저장 후 포매터 자동 실행, 위험한 명령어 사전 차단, 작업 완료 알림까지 — 원하는 시점에 원하는 처리를 끼워 넣을 수 있다.


왜 알게 됐냐면

블로그 작업을 Claude Code로 자동화하면서 생긴 문제였다. 초안 파일을 저장하고 나면 SEO 검토(키워드 배치, 소제목, 메타 디스크립션 등)를 해야 하는데, 작업이 길어지면 그냥 넘어가는 경우가 생겼다. CLAUDE.md에 "반드시 SEO 검토 실행"이라고 써둬도 소용없을 때가 있다.

AI 코딩 도구 활용법에서 다뤘던 것처럼, AI 도구는 룰 파일보다 현재 작업 흐름에 더 집중하는 경향이 있다. "이걸 AI한테 상기시켜달라고 하는 것보다 그냥 자동으로 실행되면 안 되나?" 하는 생각에 찾아봤더니 있었다. Hooks.


Claude Code Hooks가 뭔가

Claude Code 이벤트 시점에 자동으로 실행되는 쉘 명령어 설정이다.

AI에게 "이것도 해줘"라고 부탁하는 게 아니라, 클로드가 특정 동작을 할 때 내가 지정한 코드가 무조건 실행되는 구조다. LLM이 선택하는 게 아니라 결정론적으로 동작한다는 게 핵심이다. AI는 맥락에 따라 잊어버릴 수 있지만 hooks는 안 잊는다.

잠깐, "특정 동작"이 뭔가 싶을 텐데 — Claude Code는 파일을 쓸 때 Write, 파일을 수정할 때 Edit, 터미널 명령을 실행할 때 Bash 같은 내장 도구(tool)를 사용한다. 우리가 대화창에서 지시를 내리면 클로드가 이 도구들을 골라서 쓰는 구조다. Hooks는 이 도구들이 실행되기 직전이나 직후에 끼어드는 지점이다.

예를 들어 클로드가 파일을 저장(Write)한 직후에 "prettier 실행", 터미널 명령(Bash)을 실행하기 전에 "위험한 명령어면 차단" 같은 것들을 자동화할 수 있다.


Claude Code Hooks 이벤트 종류

공식 문서 기준으로 17가지 이벤트를 지원한다.

이벤트 실행 시점
SessionStart 세션 시작 또는 재개
UserPromptSubmit 프롬프트 제출 직후, 클로드 처리 전
PreToolUse 도구 실행 (차단 가능)
PostToolUse 도구 실행
PostToolUseFailure 도구 실행 실패 후
Notification Claude Code가 알림을 보낼 때
Stop 클로드가 응답을 마쳤을 때
SessionEnd 세션 종료 시
그 외 SubagentStart, PreCompact, ConfigChange 등

가장 많이 쓰는 이벤트를 용도별로 정리하면 이렇다.

PreToolUse / PostToolUse — 도구 제어

PreToolUse는 도구 실행 전에 끼어들어 차단하거나 수정할 수 있다. .env 파일 수정 시도를 막거나, Bash에서 위험한 명령어가 들어오면 실행 전에 막는 용도로 쓴다. PostToolUse는 이미 실행된 후라 차단은 안 되고, 후속 처리에 집중한다. 코드 포매터, 로그 기록, 리마인더 출력 같은 것들.

Notification — 자리 비웠을 때 알림

Claude Code는 허가가 필요하거나 입력을 기다릴 때 Notification 이벤트를 발생시킨다. 여기에 훅을 걸면 맥 기본 알림으로 받을 수 있다.

{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [{
          "type": "command",
          "command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
        }]
      }
    ]
  }
}

matcher를 빈 문자열로 두면 모든 알림에 반응한다. 클로드한테 작업 던져놓고 다른 거 하다가 알림 받는 용도로 자주 쓴다.

SessionStart (compact) — 컨텍스트 압축 후 재주입

Claude Code는 대화가 길어지면 컨텍스트를 자동으로 압축(compact)한다. 이 과정에서 중요한 세부 사항이 날아갈 수 있다. SessionStart + compact matcher를 쓰면 압축 직후 내가 원하는 내용을 다시 주입할 수 있다.

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "compact",
        "hooks": [{
          "type": "command",
          "command": "echo '현재 스프린트: 결제 모듈 리팩토링. bun 사용, npm 금지. 커밋 전 bun test 필수.'"
        }]
      }
    ]
  }
}

stdout에 출력한 내용이 클로드의 컨텍스트로 들어간다. 프로젝트 규칙이나 현재 작업 상태를 다시 상기시키는 데 유용하다.

Stop — 작업 완료 검증

클로드가 응답을 마칠 때마다 발생한다. 테스트가 통과했는지, 요청한 파일이 실제로 만들어졌는지 검증하는 용도로 쓸 수 있다. Stop 훅에서 "아직 안 됐다"고 피드백을 주면 클로드가 이어서 작업한다.


훅이 실제로 어떻게 동작하나

전체 흐름을 한 번만 이해하면 나머지는 쉽다.

Claude Code Hooks 동작 흐름 - PreToolUse 차단과 PostToolUse 후처리

훅과 Claude Code가 정보를 주고받는 방식은 단순하다.

  • Claude Code → 훅: 이벤트 발생 시 JSON을 stdin으로 넘긴다. 어떤 도구가, 어떤 파일에, 어떤 내용을 쓰려는지 담겨 있다.
  • 훅 → Claude Code: exit code와 stderr/stdout으로 응답한다.
    • exit 0: 정상, 계속 진행
    • exit 2: 차단. stderr에 쓴 내용이 클로드 피드백으로 들어간다.
    • stdout에 출력한 내용은 클로드의 컨텍스트에 추가된다 (UserPromptSubmit, SessionStart 훅에서 활용)

즉, 훅 스크립트는 stdin을 읽고 → 조건 판단 후 → exit code + 메시지로 결과를 돌려주면 된다. 언어는 무관하다. bash든 Python이든 exit code만 맞으면 된다.


Claude Code Hooks 실제 설정 방법

설정 파일 위치

훅 설정은 세 곳 중 하나에 넣는다.

파일 위치 적용 범위 git 공유
~/.claude/settings.json 내 모든 프로젝트 X
.claude/settings.json 현재 프로젝트만 O (팀 공유 가능)
.claude/settings.local.json 현재 프로젝트만 X (gitignore)

어디에 넣을지는 훅의 성격으로 판단하면 된다.

  • 전역(~/.claude/settings.json): 프로젝트와 관계없이 항상 필요한 것들. Claude Code가 입력을 기다릴 때 맥 알림을 받는 Notification 훅, .env 파일을 어디서든 보호하는 PreToolUse 훅 같은 게 여기 해당한다. 한 번 설정해두면 모든 프로젝트에 자동 적용된다.
  • 프로젝트(.claude/settings.local.json): 이 프로젝트에서만 의미 있는 것들. 이 블로그의 SEO 리마인더 훅이 대표적인 예다. drafts/ 폴더는 이 프로젝트에만 있고, 다른 프로젝트에서 같은 훅이 뜨면 오히려 노이즈다. 팀과 공유할 게 아니라면 .local.json에 넣는다.

기본 구조

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "command": "python3 /path/to/your/script.py"
          }
        ]
      }
    ]
  }
}

matcher — 정규식으로 도구를 필터링한다.

패턴 의미
"Write" Write 도구만
"Edit|Write" Edit 또는 Write
"Bash" Bash 명령 실행만
"mcp__github__.*" GitHub MCP 서버의 모든 도구
"" (빈 문자열) 해당 이벤트의 모든 경우

PreToolUse/PostToolUse에서 matcher는 tool_name을 기준으로 매칭된다. Notification에서는 알림 타입(permission_prompt, idle_prompt 등)을 기준으로 한다.

type — 훅의 실행 방식을 결정한다.

  • "command": 쉘 명령어를 직접 실행. 가장 일반적.
  • "prompt": 판단이 필요할 때 LLM(기본값 Haiku)을 호출해서 결정. 규칙이 복잡할 때 유용하다. 예: "이 커밋 메시지가 컨벤션에 맞는지 확인해줘".
  • "agent": 서브에이전트를 띄워서 파일을 읽거나 명령을 실행한 뒤 판단. 단순 텍스트 판단이 아닌 실제 코드 검증이 필요할 때 쓴다. 예: "테스트가 전부 통과하는지 실행해서 확인".

"command"가 가장 단순하고 빠르다. "prompt" / "agent"는 LLM을 추가로 호출하므로 응답 시간이 길어진다.

command: 실행할 쉘 명령어. 절대 경로를 쓰는 걸 권장한다. 프로젝트 내 스크립트를 가리킬 때는 하드코딩하거나 $CLAUDE_PROJECT_DIR 환경변수를 활용한다.

훅 스크립트가 받는 정보

훅이 실행될 때 stdin으로 JSON 데이터가 들어온다.

{
  "session_id": "abc123",
  "cwd": "/Users/me/myproject",
  "hook_event_name": "PostToolUse",
  "tool_name": "Write",
  "tool_input": {
    "file_path": "/Users/me/myproject/drafts/2026-02-23-example.md",
    "content": "..."
  }
}

여기서 tool_input.file_path를 꺼내서 어느 파일인지 확인하고, 조건에 맞을 때만 처리하면 된다. Python이나 bash로 json.load(sys.stdin) 한 줄이면 파싱은 끝난다.


실제 예시: SEO 리마인더 훅

내가 실제로 적용한 케이스다. drafts/ 폴더에 파일이 저장될 때만 SEO 검토 리마인더를 출력하는 훅이다.

스크립트 (scripts/hooks/after_write.py):

import sys, json

data = json.load(sys.stdin)
file_path = data.get("tool_input", {}).get("file_path", "")

if "drafts/" not in file_path:
    sys.exit(0)

print("""
📋 SEO 검토 실행하세요
0단계: 사실확인 (주소·영업시간·가격)
1~7단계: 키워드 → 제목 → 첫 문단 → 소제목 → alt → 내부링크 → 태그
최종: 체크리스트 11항목
""")

설정 (.claude/settings.local.json):

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "command": "python3 /path/to/project/scripts/hooks/after_write.py"
          }
        ]
      }
    ]
  }
}

drafts/가 경로에 없으면 sys.exit(0)으로 조용히 종료. 있으면 메시지 출력. 단순한데 제 역할은 한다.


실제 예시 2: 민감 파일 보호 훅

클로드가 .envpackage-lock.json 같은 파일을 수정하려 할 때 자동으로 차단하는 훅이다. PreToolUse를 써야 "실행 전에" 막을 수 있다.

스크립트 (scripts/hooks/protect_files.sh):

#!/bin/bash

INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | python3 -c "
import sys, json
d = json.load(sys.stdin)
print(d.get('tool_input', {}).get('file_path', ''))
")

PROTECTED=(".env" ".env.local" "package-lock.json" ".git/")

for pattern in "${PROTECTED[@]}"; do
  if [[ "$FILE_PATH" == *"$pattern"* ]]; then
    echo "차단: $FILE_PATH 는 보호된 파일입니다. 직접 수정이 필요하면 수동으로 진행하세요." >&2
    exit 2
  fi
done

exit 0

설정 (.claude/settings.json — 팀 공유 목적이라면 이 위치):

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "bash $CLAUDE_PROJECT_DIR/scripts/hooks/protect_files.sh"
          }
        ]
      }
    ]
  }
}

exit 2로 끝나면 stderr에 쓴 메시지가 클로드 피드백으로 전달된다. 클로드는 "이 파일은 수정할 수 없다"는 맥락을 받고 다른 방법을 찾는다. exit 0이면 그냥 진행.


종료 코드(exit code)로 차단·허용 제어

훅 스크립트의 종료 코드에 따라 Claude Code가 다르게 반응한다.

종료 코드 동작
0 정상. 이어서 진행
2 차단. stderr 내용이 클로드에게 피드백으로 전달됨
그 외 진행은 하되, 에러는 verbose 모드에서만 표시

PostToolUse는 이미 실행된 뒤라 차단 의미는 없다. 차단이 필요하면 PreToolUse를 써야 한다.

.env 같은 민감 파일을 클로드가 수정하지 못하게 막는 실제 예시를 보면 이렇다.

#!/bin/bash
# scripts/hooks/protect_files.sh

INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('tool_input',{}).get('file_path',''))")

PROTECTED=(".env" ".env.local" "package-lock.json")

for pattern in "${PROTECTED[@]}"; do
  if [[ "$FILE_PATH" == *"$pattern"* ]]; then
    echo "차단: $FILE_PATH 는 보호된 파일입니다. 직접 수정하세요." >&2
    exit 2
  fi
done

exit 0

exit 2로 끝나면 stderr에 쓴 메시지가 클로드에게 피드백으로 전달된다. 클로드는 "이 파일은 수정할 수 없다"는 걸 알고 다른 방법을 찾는다. exit 0이면 그냥 진행.


헤매기 쉬운 포인트 정리

설정 파일을 직접 수정하면 즉시 반영이 안 된다.

Claude Code가 실행 중인 상태에서 settings 파일을 직접 편집하면 그 변경사항이 바로 적용되지 않는다. 두 가지 방법으로 반영할 수 있다.

방법 1 — /hooks 명령 사용: Claude Code 대화창에서 /hooks라고 입력하면 인터랙티브 메뉴가 뜬다. 여기서 현재 등록된 훅 목록을 확인하거나 새로 추가할 수 있다. 이 메뉴를 한 번 열었다 닫기만 해도 설정 파일 변경사항이 즉시 반영된다.

방법 2 — 세션 재시작: Claude Code를 종료하고 다시 시작하면 자동으로 최신 설정을 읽어온다.

처음부터 /hooks 메뉴에서 훅을 추가하면 파일을 직접 편집할 필요 없이 바로 반영된다. 이벤트 선택 → matcher 입력 → 명령어 입력 → 저장 위치 선택 순서로 진행된다.

Claude Code /hooks 메뉴 - 등록된 훅 이벤트 목록

matcher는 대소문자 구분이 있다.
write가 아니라 Write다. 매칭이 안 되면 훅이 실행 자체가 안 된다.

스크립트 실행 에러가 있으면 훅이 조용히 실패한다.
Ctrl+O로 verbose 모드를 켜면 에러 내용을 볼 수 있다.

shell profile의 echo 문이 JSON 파싱 에러를 일으킬 수 있다.
훅은 비대화형 셸에서 실행된다. ~/.zshrc에 무조건 echo가 있으면 stdout이 오염된다.

# ~/.zshrc — 이렇게 조건 달아줘야 한다
if [[ $- == *i* ]]; then
  echo "Shell ready"
fi

자주 묻는 질문

Q. 훅이 제대로 설정됐는지 어떻게 확인하나?

Claude Code 대화창에서 /hooks를 입력하면 된다. 현재 등록된 훅 목록이 이벤트별로 표시된다. 여기서 matcher, 명령어, 저장 위치도 확인할 수 있고, 불필요한 훅을 삭제하거나 새 훅을 추가하는 것도 이 메뉴에서 할 수 있다.

Q. PostToolUse와 PreToolUse 중 뭘 써야 하나?

무언가를 막아야 한다면 PreToolUse. 실행 후 후처리가 목적이라면 PostToolUse. 코드 포매터, 로그 기록, 알림은 대부분 PostToolUse로 충분하다.

Q. 모든 프로젝트에 같은 훅을 적용하고 싶다면?

~/.claude/settings.json에 넣으면 된다. 이 파일은 내 머신의 모든 Claude Code 세션에 적용된다. 알림 훅, .env 보호 훅처럼 "어느 프로젝트든 항상 켜두고 싶은 것"은 전역에 두고, 특정 프로젝트 규칙은 그 프로젝트의 .claude/settings.local.json에 분리하는 게 깔끔하다.

Q. 훅 스크립트를 Python 말고 다른 언어로 짤 수 있나?

된다. bash, Node.js, 뭐든 실행 가능한 명령어면 된다. stdin으로 JSON이 들어오고 stdout/stderr로 결과를 내보내는 구조만 맞추면 된다.

Q. 훅이 실행 중에 오래 걸리면 어떻게 되나?

기본 타임아웃은 10분이다. 설정에서 timeout 필드(초 단위)로 조정할 수 있다.


정리

  • Hooks = Claude Code 이벤트 시점에 자동 실행되는 쉘 명령어
  • 17가지 이벤트, matcher로 필터링, stdin으로 JSON 받아서 처리
  • 설정 파일 위치로 스코프 결정 (전역 / 프로젝트 / 로컬)
  • 차단이 필요하면 PreToolUse + exit code 2
  • 후처리 자동화는 PostToolUse

AI 코딩 도구를 쓰면서 반복 작업을 AI에게 부탁하기 전에, hooks로 걸 수 있는 건 걸어두는 게 낫다. AI는 맥락에 따라 넘어갈 수 있지만, hooks는 빠지지 않는다. Hooks를 포함해 CLAUDE.md·Skills·Subagents를 팀 단위 인프라로 묶는 방법이 궁금하다면 하네스 엔지니어링 — AI 코딩 도구를 팀 시스템으로 바꾸는 법을 참고. Claude Code 자체를 IntelliJ와 어떻게 조합해서 쓰는지 궁금하다면 Cursor+IntelliJ vs Claude Code+IntelliJ 백엔드 조합 비교를 참고.


출처

반응형