[클로드 코드] (7) 훅의 정의

훅은 클로드 코드의 자동화 기능 중 하나예요. 이 글에서는 훅이 정확히 무엇인지, 어떻게 작동하는지, 그리고 당신의 개발 파이프라인에서 어떻게 활용될 수 있는지 이해하여 클로드 코드를 더 고도화 시킨 방식으로 사용해보세요.

검은 배경에 픽셀 스타일 CLAUDE CODE 로고와 "클로드 코드 (7) 훅의 정의" 제목이 있는 썸네일 이미지.

클로드 코드(Claude Code)를 사용하다 보면, “이 파일은 절대 수정하지 못하게 막고 싶은데”와 같은 생각이 드는 순간이 찾아와요. 단순히 프롬프트로 부탁하는 것만으로는 매번 일관된 결과를 보장하기 어려운 경우가 많죠.

이럴 때 사용할 수 있는 것이 바로 훅(Hook)이에요. 훅은 클로드 코드가 도구를 실행하기 전이나 후에, 여러분이 원하는 명령어를 자동으로 끼워 넣을 수 있게 해주는 기능이에요. 마치 공장의 조립 라인에 품질 검사 공정을 추가하는 것처럼, AI의 작업 흐름 사이사이에 여러분만의 규칙을 삽입할 수 있어요.

이 글에서는 훅의 개념부터 설정 방법, 각 훅 유형의 동작 원리, 그리고 실전 디버깅 팁까지 하나씩 살펴볼게요.


1. 훅(Hook)이란 무엇인가

훅이란, 클로드 코드가 특정 작업을 수행하기 전이나 후에 자동으로 실행되는 커스텀 커맨드(사용자 정의 명령어, Custom Command)를 의미해요.

클로드 코드와 대화할 때의 일반적인 흐름을 먼저 떠올려 보면 이해가 쉬워요.

사용자가 질문 입력
    → 클로드 모델이 응답 생성
    → 필요 시 도구(Tool) 사용 결정
    → 도구 실행
    → 결과 반환

훅은 이 흐름에서 “도구 실행” 단계의 앞뒤에 끼어드는 역할을 해요. 아래 다이어그램을 보면 좀 더 직관적으로 이해할 수 있어요.

사용자: "main.go 파일에 뭐가 작성되어 있어?"
    │
    ▼
┌─────────────┐
│ Claude Code │ ──→ 클로드 모델에게 질문 전달
└─────────────┘
    │
    │  클로드: "ReadFile: main.go" (파일 읽기 도구 사용 결정)
    │
    ▼
┌──────────────────┐
│ PreToolUse 훅 실행 │  ← 도구 실행 "전"에 끼어드는 지점
└──────────────────┘
    │
    ▼
┌──────────────────┐
│ 도구 실행 (파일 읽기) │
└──────────────────┘
    │
    ▼
┌───────────────────┐
│ PostToolUse 훅 실행 │  ← 도구 실행 "후"에 끼어드는 지점
└───────────────────┘
    │
    ▼
사용자에게 결과 반환

쉽게 말해, 훅은 클로드 코드의 작업 파이프라인에 여러분이 원하는 자동화 로직을 삽입하는 통로라고 볼 수 있어요. 이를 통해 다음과 같은 작업이 가능해져요.

훅은 “클로드에게 부탁하는 것”이 아니라, “클로드의 행동에 규칙을 강제하는 것”에 가까워요. 프롬프트가 ‘요청’이라면, 훅은 ‘시스템 정책’이라고 비유할 수 있어요. 식당에서 “소금 좀 적게 넣어주세요”라고 부탁하는 것과, 주방에 아예 소금통의 구멍 크기를 줄여놓는 것의 차이라고 생각하면 돼요.


2. 훅을 설정하는 곳

훅은 클로드 코드의 설정 파일(Settings File)에 JSON 형식으로 정의해요. 설정 파일을 직접 편집하거나, 클로드 코드 내에서 /hooks 명령어를 입력해 대화형으로 설정할 수도 있어요.

설정 파일은 적용 범위에 따라 세 가지 레벨로 나뉘어요.

레벨 파일 경로 적용 범위 팀 공유 여부
글로벌(Global) ~/.claude/settings.json 모든 프로젝트에 적용 X (개인 전용)
프로젝트(Project) .claude/settings.json 해당 프로젝트에 적용 O (Git에 커밋하여 공유)
프로젝트 로컬(Project Local) .claude/settings.local.json 해당 프로젝트에 적용 X (개인 전용, 커밋 제외)

1) 글로벌 설정

~/.claude/settings.json에 작성하면, 여러분이 어떤 프로젝트에서 클로드 코드를 사용하든 동일하게 적용돼요. 예를 들어, “어떤 프로젝트에서든 .env 파일은 절대 읽지 못하게 하고 싶다”는 정책이 있다면 글로벌 설정에 훅을 추가하는 것이 적절해요.

2) 프로젝트 설정

.claude/settings.json은 프로젝트 루트에 위치하며, Git에 커밋할 수 있어요. 팀원들과 동일한 훅 규칙을 공유하고 싶을 때 유용해요. “우리 팀은 파일 수정 후 반드시 Prettier를 돌린다”와 같은 팀 단위 컨벤션을 강제할 수 있어요.

3) 프로젝트 로컬 설정

.claude/settings.local.json은 프로젝트 단위로 적용되지만, Git에 커밋되지 않는 개인 전용 설정이에요. “나만 디버깅용 로그를 남기고 싶다”거나 “나만의 커스텀 린터를 추가로 돌리고 싶다”는 경우에 적합해요.

이 세 가지 레벨은 마치 회사의 규칙 체계와 비슷해요. 글로벌 설정은 ‘전사 보안 정책’처럼 어디서든 적용되고, 프로젝트 설정은 ‘팀 코딩 컨벤션’처럼 팀원 모두가 따르며, 프로젝트 로컬 설정은 ‘개인 업무 스타일’처럼 나만의 워크플로를 커스터마이징하는 데 쓰이는 셈이에요.


3. 훅의 기본 구조 이해하기

훅은 JSON 형식으로 작성되며, 크게 두 가지 핵심 훅 유형이 있어요.

1) 훅의 기본 형태

하나의 훅 설정은 다음과 같은 구조를 가져요.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "도구 이름",
        "hooks": [
          {
            "type": "command",
            "command": "실행할 명령어"
          }
        ]
      }
    ]
  }
}

각 키의 의미를 하나씩 살펴볼게요.

설명
hooks 최상위 키. 모든 훅 설정을 감싸는 객체예요.
PreToolUse / PostToolUse 훅의 실행 시점을 결정해요. 배열([]) 형태로, 여러 개의 훅 규칙을 담을 수 있어요.
matcher 어떤 도구에 대해 훅을 실행할지 지정해요. "Read", "Write" 등 도구 이름을 넣고, 파이프(\|)로 여러 도구를 묶을 수 있어요. "*"를 사용하면 모든 도구에 대해 동작해요.
hooks (내부 배열) 실제로 실행할 명령어들의 목록이에요.
type 현재는 "command"만 지원돼요.
command 실행할 셸 명령어 또는 스크립트 경로예요.

2) 여러 개의 훅을 등록하는 방법

하나의 훅 종류(예: PreToolUse)에 여러 개의 훅 규칙을 등록할 수 있어요. 배열 안에 객체를 추가하면 돼요.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Read",
        "hooks": [
          {
            "type": "command",
            "command": "node /scripts/check_read_permission.js"
          }
        ]
      },
      {
        "matcher": "Write",
        "hooks": [
          {
            "type": "command",
            "command": "node /scripts/validate_write_target.js"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write|Edit|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "npx prettier --write ."
          }
        ]
      }
    ]
  }
}

위 예시에서는 세 가지 훅 규칙이 등록되어 있어요.

3) matcher에서 파이프(|)로 여러 도구 지정하기

matcher 필드에서 파이프 기호(|)를 사용하면, 하나의 훅 규칙을 여러 도구에 동시에 적용할 수 있어요.

"matcher": "Write|Edit|MultiEdit"

이렇게 작성하면 Write, Edit, MultiEdit 중 어떤 도구가 호출되더라도 동일한 훅이 실행돼요. 매번 도구마다 따로 훅을 등록할 필요가 없어 설정이 간결해져요.

반대로, 모든 도구에 대해 훅을 걸고 싶다면 와일드카드(*)를 사용해요.

"matcher": "*"

훅의 JSON 구조는 처음 보면 중첩이 깊어서 복잡해 보일 수 있지만, 실제로는 “언제(PreToolUse/PostToolUse) → 어떤 도구에(matcher) → 무엇을 실행할지(command)”라는 세 가지 질문에 답하는 것뿐이에요. 택배 배송 시스템에 비유하면, “배송 출발 전(Pre) 또는 배송 완료 후(Post)에 → 특정 지역 배송건에 한해(matcher) → 추가 검수를 실행한다(command)”와 같은 구조라고 생각하면 쉬워요.


4. 도구 실행 전 훅(PreToolUse Hook)

도구 실행 전 훅(PreToolUse Hook)은 클로드 코드가 특정 도구를 호출하려는 순간, 그 실행이 일어나기 직전에 개입하는 훅이에요.

이 훅의 가장 큰 특징은 도구 실행을 허용할 수도 있고, 차단할 수도 있다는 점이에요. 단순히 “실행 전에 무언가를 한다”는 것을 넘어, 실행 자체를 막고 클로드에게 에러 메시지를 돌려보내는 게이트키퍼(Gatekeeper) 역할을 수행할 수 있어요.

1) PreToolUse 훅이 할 수 있는 두 가지

PreToolUse 훅은 실행 결과에 따라 두 가지 분기를 만들어 내요.

클로드가 도구 사용을 결정
    │
    ▼
┌──────────────────┐
│ PreToolUse 훅 실행 │
└──────────────────┘
    │
    ├─ 통과(Pass) ──→ 도구가 정상적으로 실행됨
    │
    └─ 차단(Block) ──→ 도구 실행이 중단되고,
                        에러 메시지가 클로드에게 전달됨

2) 활용 시나리오: 민감한 파일 접근 차단

팀에서 운영 중인 프로젝트에 .env 파일이나 secrets/ 디렉토리처럼 절대 AI가 읽거나 수정해서는 안 되는 파일이 있다고 가정해 볼게요.

프롬프트로 “이 파일은 건드리지 마”라고 말할 수도 있지만, 대화가 길어지거나 맥락이 바뀌면 클로드가 이를 잊어버릴 수 있어요. PreToolUse 훅을 사용하면 시스템 레벨에서 확실하게 차단할 수 있어요.

먼저 훅 설정부터 살펴볼게요.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Read|Write|Edit|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "node /scripts/block_sensitive_files.js"
          }
        ]
      }
    ]
  }
}

이 설정은 파일을 읽거나(Read) 쓰거나(Write) 수정하는(Edit, MultiEdit) 모든 도구가 실행되기 전에 block_sensitive_files.js 스크립트를 먼저 실행해요.

그렇다면 이 스크립트 안에서는 무슨 일이 벌어질까요? 핵심은 stdin으로 전달되는 데이터에서 대상 파일 경로를 꺼내 검사하는 것이에요.

앞서 섹션 7에서 다룰 내용이지만, 훅 명령어는 실행될 때 클로드 코드로부터 JSON 데이터를 stdin으로 전달받아요. PreToolUse 훅의 경우, 이 JSON에는 tool_input이라는 필드가 포함되어 있고, 여기에 도구가 작업하려는 파일 경로 등의 정보가 담겨 있어요.

아래는 block_sensitive_files.js의 구현 예시예요.

// block_sensitive_files.js
// stdin으로 전달된 JSON에서 파일 경로를 확인하고,
// 민감한 파일이면 에러를 반환하여 도구 실행을 차단합니다.

const BLOCKED_PATTERNS = [
  /\.env$/,           // .env 파일
  /\.env\..+$/,       // .env.local, .env.production 등
  /secrets\//,        // secrets/ 디렉토리 하위 모든 파일
  /\.pem$/,           // 인증서 파일
  /credentials\.json/ // 인증 정보 파일
];

// stdin에서 JSON 데이터 읽기
let input = "";
process.stdin.on("data", (chunk) => {
  input += chunk;
});

process.stdin.on("end", () => {
  const data = JSON.parse(input);

  // tool_input에서 파일 경로 추출
  // (도구에 따라 file_path, path 등 필드명이 다를 수 있음)
  const filePath = data.tool_input?.file_path
    || data.tool_input?.path
    || "";

  // 차단 대상 패턴과 비교
  const isBlocked = BLOCKED_PATTERNS.some(
    (pattern) => pattern.test(filePath)
  );

  if (isBlocked) {
    // 에러 메시지를 출력하고 비정상 종료 (exit code 1)
    // → 도구 실행이 차단되고, 이 메시지가 클로드에게 전달됨
    console.error(
      `[접근 차단] "${filePath}"은(는) 보안 정책에 의해 접근이 제한된 파일입니다. ` +
      `이 파일을 직접 읽거나 수정하지 말고, 다른 방법을 사용해 주세요.`
    );
    process.exit(1);
  }

  // 차단 대상이 아니면 정상 종료 (exit code 0)
  // → 도구 실행이 예정대로 진행됨
  process.exit(0);
});

전체 흐름을 단계별로 정리하면 다음과 같아요.

사용자: ".env 파일 내용 보여줘"
    │
    ▼
클로드: Read 도구로 .env 파일 읽기를 시도
    │
    ▼
┌─────────────────────────────────────────┐
│ PreToolUse 훅 실행                        │
│                                         │
│ 1. stdin으로 JSON 데이터 수신               │
│    → tool_input.file_path = ".env"      │
│                                         │
│ 2. BLOCKED_PATTERNS와 대조                │
│    → /\.env$/ 패턴에 매칭됨                │
│                                         │
│ 3. 에러 메시지 출력 + exit code 1 반환       │
└─────────────────────────────────────────┘
    │
    ▼
Read 도구 실행이 차단됨
    │
    ▼
클로드가 에러 메시지를 수신:
"[접근 차단] .env은(는) 보안 정책에 의해 접근이 제한된 파일입니다."
    │
    ▼
클로드: "해당 파일은 보안 정책으로 접근이 제한되어 있어요.
        다른 방법으로 도와드릴까요?"

여기서 주목할 점은 에러 메시지의 내용이에요. 단순히 “차단됨”이라고만 하면, 클로드는 왜 차단되었는지 모른 채 같은 시도를 반복할 수 있어요. 에러 메시지에 차단 이유와 대안 방향을 함께 명시하면, 클로드가 이를 이해하고 사용자에게 적절한 안내를 제공할 수 있어요.


5. 도구 실행 후 훅(PostToolUse Hook)

도구 실행 후 훅(PostToolUse Hook)은 클로드 코드가 도구를 성공적으로 실행한 직후에 동작하는 훅이에요.

PreToolUse 훅과의 결정적인 차이가 있어요. PostToolUse 훅은 이미 실행이 완료된 후에 동작하기 때문에, 해당 작업을 되돌리거나 차단할 수는 없어요. 대신, 실행 결과를 기반으로 후속 작업을 수행하거나, 클로드에게 추가 피드백을 제공하는 용도로 사용돼요.

1) PostToolUse 훅이 할 수 있는 것들

PostToolUse 훅은 주로 다음 두 가지 역할을 담당해요.

도구 실행 완료
    │
    ▼
┌───────────────────┐
│ PostToolUse 훅 실행 │
└───────────────────┘
    │
    ├─ 후속 작업 수행 (포매팅, 테스트, 로깅 등)
    │
    └─ 클로드에게 피드백 전달 (린트 결과, 경고 메시지 등)

2) 활용 시나리오: 파일 수정 후 자동 포매팅

팀에서 코드 스타일을 Prettier나 Black 같은 포매터로 통일하고 있다면, 클로드가 파일을 수정할 때마다 수동으로 포매터를 돌릴 필요 없이 훅으로 자동화할 수 있어요.

먼저 훅 설정을 살펴볼게요.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "node /scripts/auto_format.js"
          }
        ]
      }
    ]
  }
}

이 설정은 Write, Edit, MultiEdit 도구가 실행된 직후에 auto_format.js 스크립트를 자동으로 실행해요. 단순히 npx prettier --write .를 바로 실행할 수도 있지만, 스크립트로 감싸면 수정된 파일만 골라서 포매팅하거나, 파일 확장자에 따라 다른 포매터를 적용하는 등 유연한 처리가 가능해져요.

아래는 auto_format.js의 구현 예시예요.

// auto_format.js
// stdin으로 전달된 JSON에서 수정된 파일 경로를 추출하고,
// 해당 파일에 적합한 포매터를 자동으로 실행합니다.

const { execSync } = require("child_process");

let input = "";
process.stdin.on("data", (chunk) => {
  input += chunk;
});

process.stdin.on("end", () => {
  const data = JSON.parse(input);

  // tool_input에서 수정된 파일 경로 추출
  const filePath = data.tool_input?.file_path
    || data.tool_input?.path
    || "";

  if (!filePath) {
    process.exit(0); // 파일 경로가 없으면 아무 작업 없이 종료
  }

  // 확장자에 따라 적합한 포매터 선택
  const formatters = {
    js:   `npx prettier --write "${filePath}"`,
    ts:   `npx prettier --write "${filePath}"`,
    jsx:  `npx prettier --write "${filePath}"`,
    tsx:  `npx prettier --write "${filePath}"`,
    css:  `npx prettier --write "${filePath}"`,
    json: `npx prettier --write "${filePath}"`,
    py:   `black "${filePath}"`,
    go:   `gofmt -w "${filePath}"`,
  };

  const ext = filePath.split(".").pop();
  const command = formatters[ext];

  if (!command) {
    // 지원하지 않는 확장자면 건너뛰기
    console.log(`[포매팅 건너뜀] "${filePath}" - 지원하지 않는 파일 형식`);
    process.exit(0);
  }

  try {
    execSync(command, { stdio: "pipe" });
    // 포매팅 결과를 클로드에게 피드백으로 전달
    console.log(`[포매팅 완료] "${filePath}" - ${ext} 포매터 적용됨`);
  } catch (error) {
    // 포매팅 실패 시에도 클로드에게 알려줌
    console.error(
      `[포매팅 실패] "${filePath}" - ${error.message}`
    );
  }

  process.exit(0);
});

전체 흐름을 단계별로 정리하면 다음과 같아요.

사용자: "로그인 API 엔드포인트 만들어줘"
    │
    ▼
클로드: Write 도구로 auth.ts 파일 생성
    │
    ▼
Write 도구 실행 완료 (파일이 디스크에 저장됨)
    │
    ▼
┌─────────────────────────────────────────┐
│ PostToolUse 훅 실행                       │
│                                         │
│ 1. stdin으로 JSON 데이터 수신               │
│    → tool_input.file_path = "auth.ts"   │
│                                         │
│ 2. 확장자 추출: "ts"                       │
│    → 매칭되는 포매터: npx prettier --write  │
│                                         │
│ 3. Prettier 실행                         │
│    → 들여쓰기, 세미콜론, 줄바꿈 등 정리         │
│                                         │
│ 4. 결과 출력:                             │
│    "[포매팅 완료] auth.ts - ts 포매터 적용됨" │
└─────────────────────────────────────────┘
    │
    ▼
클로드가 피드백을 수신:
"[포매팅 완료] auth.ts - ts 포매터 적용됨"
    │
    ▼
클로드: 포매팅이 자동 적용된 상태에서 후속 작업 계속 진행

이 접근 방식에는 몇 가지 실용적인 이점이 있어요.

물론 프로젝트가 단일 언어로 구성되어 있거나 간단한 설정만 필요하다면, 스크립트 없이 명령어를 직접 넣는 방식도 충분해요.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "npx prettier --write ."
          }
        ]
      }
    ]
  }
}

이 경우 모든 수정 후에 프로젝트 전체에 Prettier가 실행돼요. 프로젝트 규모가 작다면 이 방식이 설정도 간단하고 관리하기 편해요.


6. 그 외 내장 훅 살펴보기

지금까지 살펴본 PreToolUsePostToolUse는 도구 실행의 전후에 개입하는 훅이었어요. 하지만 클로드 코드에는 도구 실행 외에도 다양한 이벤트 시점에 끼어들 수 있는 내장 훅들이 더 존재해요.

훅 이름 실행 시점 활용 예시
Notification 클로드 코드가 알림을 보낼 때 (도구 사용 권한 요청 시, 또는 60초 이상 유휴 상태일 때) 슬랙(Slack)으로 알림 전달, 유휴 상태 로깅
Stop 클로드 코드가 응답을 완료했을 때 작업 완료 알림 전송, 세션 요약 로그 생성
SubagentStop 서브에이전트(UI에서 “Task”로 표시되는 하위 작업)가 완료되었을 때 하위 작업별 결과 기록, 품질 체크
PreCompact 컴팩트(Compact) 작업이 실행되기 전 (수동 또는 자동) 컴팩트 전 대화 내용 백업, 중요 컨텍스트 별도 저장
UserPromptSubmit 사용자가 프롬프트를 제출했을 때 (클로드가 처리하기 전) 입력 필터링, 프롬프트 로깅, 자동 컨텍스트 주입
SessionStart 세션이 시작되거나 재개될 때 환경 변수 초기화, 작업 디렉토리 정리
SessionEnd 세션이 종료될 때 작업 로그 저장, 리소스 정리

1) Notification 훅

클로드 코드는 두 가지 상황에서 알림을 발생시켜요. 하나는 도구 사용에 대한 권한이 필요할 때이고, 다른 하나는 60초 이상 아무 활동 없이 대기하고 있을 때예요. Notification 훅을 설정하면, 이 알림이 발생하는 순간에 원하는 명령어를 실행할 수 있어요. 예를 들어, 장시간 작업을 맡겨두고 자리를 비웠을 때 슬랙이나 데스크톱 알림으로 상태를 받아보는 용도로 활용할 수 있어요.

2) Stop 훅과 SubagentStop 훅

Stop 훅은 클로드 코드가 전체 응답을 마쳤을 때 실행돼요. 복잡한 작업을 맡겨두고 완료 시점에 알림을 받거나, 최종 결과물에 대한 자동 검증을 수행하고 싶을 때 유용해요. SubagentStop은 좀 더 세분화된 시점에 동작해요. 클로드 코드는 복잡한 작업을 처리할 때 내부적으로 서브에이전트(Subagent)라는 하위 작업 단위를 생성하는데, 이 하위 작업 하나하나가 완료될 때마다 훅이 실행돼요. UI에서는 “Task”로 표시되는 항목들이 이에 해당해요.

3) PreCompact 훅

클로드 코드는 대화가 길어지면 자동 또는 수동으로 컴팩트(Compact) 작업을 수행해요. 컴팩트란 이전 대화 내용을 요약하여 컨텍스트 윈도우(Context Window)를 확보하는 과정이에요. PreCompact 훅을 설정하면, 이 요약이 일어나기 직전에 원하는 작업을 실행할 수 있어요. 예를 들어, 컴팩트 전에 현재까지의 대화 원문을 별도 파일로 백업해 두면, 요약 과정에서 누락될 수 있는 세부 내용을 보존할 수 있어요.

4) UserPromptSubmit 훅

UserPromptSubmit은 사용자가 프롬프트를 입력하고 제출한 순간, 클로드가 이를 처리하기 전에 실행되는 훅이에요. 이 훅은 사용자 입력에 대한 사전 처리가 필요한 경우에 활용할 수 있어요. 예를 들어, 모든 프롬프트를 로그 파일에 기록해 두거나, 특정 키워드가 포함된 입력에 자동으로 추가 컨텍스트를 주입하는 시나리오를 생각해 볼 수 있어요.

5) SessionStart 훅과 SessionEnd 훅

SessionStart는 새 세션을 시작하거나 기존 세션을 재개할 때, SessionEnd는 세션이 종료될 때 실행돼요.

세션의 시작과 끝에 환경을 초기화하거나 정리하는 작업을 자동화하고 싶을 때 적합해요. 예를 들어, 세션 시작 시 특정 환경 변수를 설정하거나, 세션 종료 시 임시 파일을 삭제하는 스크립트를 연결할 수 있어요.

PreToolUsePostToolUse가 “작업 단위”의 전후를 제어한다면, 나머지 훅들은 “세션 전체의 라이프사이클”을 관리하는 역할을 한다고 볼 수 있어요. 하루의 업무에 비유하면, SessionStart는 출근해서 컴퓨터를 켜는 것, UserPromptSubmit은 업무 요청을 접수하는 것, PreToolUse/PostToolUse는 실제 작업 수행의 전후 점검, Stop은 하나의 업무를 마무리 짓는 것, SessionEnd는 퇴근하며 정리하는 것에 대응돼요.


7. 훅 디버깅 팁: stdin 입력 구조 확인하기

훅을 작성하다 보면 한 가지 어려운 점을 만나게 돼요. 훅에 연결한 명령어(command)가 실행될 때, 표준 입력(stdin)으로 전달되는 데이터의 구조가 훅 유형과 대상 도구에 따라 달라진다는 점이에요.

1) stdin 입력이 달라지는 이유

훅 명령어는 실행될 때 클로드 코드로부터 JSON 형태의 데이터를 stdin으로 전달받아요. 문제는 이 JSON의 구조가 고정되어 있지 않다는 것이에요.

예를 들어, PostToolUse 훅이 TodoWrite 도구에 대해 실행될 때의 stdin은 다음과 같은 구조를 가질 수 있어요.

{
  "session_id": "9ecf22fa-edf8-4332-ae85-b6d5456eda64",
  "transcript_path": "<트랜스크립트 경로>",
  "hook_event_name": "PostToolUse",
  "tool_name": "TodoWrite",
  "tool_input": {
    "todos": [
      {
        "content": "README 작성하기",
        "status": "pending",
        "priority": "medium",
        "id": "1"
      }
    ]
  },
  "tool_response": {
    "oldTodos": [],
    "newTodos": [
      {
        "content": "README 작성하기",
        "status": "pending",
        "priority": "medium",
        "id": "1"
      }
    ]
  }
}

반면, Stop 훅이 실행될 때의 stdin은 훨씬 간결해요.

{
  "session_id": "af9f50b6-f042-4773-b3e2-c3a4814765ce",
  "transcript_path": "<트랜스크립트 경로>",
  "hook_event_name": "Stop",
  "stop_hook_active": false
}

이처럼 훅 유형과 도구 조합에 따라 데이터 구조가 크게 달라지기 때문에, 자신이 작성하는 훅 스크립트가 실제로 어떤 입력을 받게 되는지 미리 확인하는 것이 중요해요.

2) 디버깅용 헬퍼 훅 만들기

이 문제를 해결하는 가장 실용적인 방법은, stdin으로 들어오는 데이터를 파일에 그대로 저장하는 헬퍼 훅을 먼저 만들어 보는 것이에요.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "jq . > post-tool-use-log.json"
          }
        ]
      }
    ]
  }
}

이 훅은 모든 도구(*)의 PostToolUse 시점에 실행되며, stdin으로 전달된 JSON 데이터를 jq로 정렬하여 post-tool-use-log.json 파일에 저장해요.

같은 방식으로 다른 훅 유형에도 적용할 수 있어요.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "jq . > pre-tool-use-log.json"
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "jq . > stop-log.json"
          }
        ]
      }
    ]
  }
}

이렇게 로그 파일을 남겨두면, 실제 훅 스크립트를 작성하기 전에 입력 데이터의 구조를 정확히 파악할 수 있어요. 어떤 필드가 존재하는지, 어떤 값이 들어오는지 눈으로 확인한 뒤에 스크립트를 작성하면 시행착오를 크게 줄일 수 있어요.

3) 디버깅 워크플로 정리

헬퍼 훅을 활용한 디버깅 순서를 정리하면 다음과 같아요.

  1. 확인하고 싶은 훅 유형(예: PostToolUse)에 jq . > 로그파일.json 헬퍼 훅을 등록해요.
  2. 클로드 코드를 사용하여 해당 훅이 트리거될 만한 작업을 수행해요.
  3. 생성된 로그 파일을 열어 stdin 데이터의 구조를 확인해요.
  4. 확인한 구조를 바탕으로 실제 훅 스크립트를 작성해요.
  5. 헬퍼 훅을 제거하고, 실제 훅으로 교체해요.

훅 디버깅은 새로운 API를 연동할 때의 접근법과 같아요. 처음부터 완성된 코드를 작성하기보다, 먼저 API가 어떤 응답을 반환하는지 console.log로 찍어보는 것이 효율적이듯이, 훅도 먼저 stdin 데이터를 파일로 떨궈보고 구조를 파악한 뒤에 로직을 작성하는 것이 훨씬 수월해요.


마무리

클로드 코드의 훅(Hook)은 AI 코딩 에이전트의 동작을 프롬프트 수준이 아닌 시스템 수준에서 제어할 수 있게 해주는 기능이에요. 이 글에서 다룬 내용을 간략히 정리해 볼게요.

구분 핵심 포인트
훅이란 클로드 코드의 작업 전후에 자동 실행되는 사용자 정의 명령어
설정 위치 글로벌, 프로젝트, 프로젝트 로컬 세 가지 레벨
PreToolUse 도구 실행 전 개입. 허용 또는 차단 가능
PostToolUse 도구 실행 후 개입. 후속 작업 및 피드백 제공
기타 내장 훅 Notification, Stop, SubagentStop, PreCompact, UserPromptSubmit, SessionStart, SessionEnd
디버깅 팁 jq . > log.json 헬퍼 훅으로 stdin 구조를 먼저 파악

훅을 잘 활용하면, 코드 품질 관리부터 보안 정책 적용, 워크플로 자동화까지 다양한 영역에서 클로드 코드를 더 안전하고 효율적으로 운용할 수 있어요. 처음에는 간단한 포매팅 훅 하나부터 시작해 보시고, 점차 팀의 필요에 맞게 확장해 나가보세요.

클로드 코드 시리즈

(1) 클로드 코드와 클로드 코드의 작동 방식

(2) 내장 도구와 MCP 작동 방식

(3) 기본 사용법 및 설정

(4) 커맨드(명령어, Command)

(5) MCP 연결과 권한 설정

(6) 깃헙 연동

(7) 훅의 정의

(8) 훅 생성 및 실행 방법

(9) 클로드 코드 SDK 설치법