Claude Code 훅이란 무엇인가?

Claude Code를 사용하다 보면 반복적으로 해야 하는 일들이 생깁니다. 파일을 수정할 때마다 포맷팅을 실행해야 한다거나, 커밋 전에 반드시 테스트를 돌려야 한다거나, 위험한 명령어가 실행될 때 경고를 받고 싶다거나 하는 것들입니다. 이런 자동화를 가능하게 하는 것이 바로 훅(Hook) 시스템입니다.

훅은 Claude Code가 특정 동작을 수행하는 시점에 자동으로 실행되는 셸 명령어입니다. 예를 들어 Claude가 파일을 수정할 때마다 자동으로 ESLint를 실행하거나, Claude가 작업을 완료할 때 빌드를 검증하는 식으로 활용할 수 있습니다.

훅은 Claude가 아닌 Claude Code 하네스(harness)가 실행합니다. 즉, 훅 명령어는 Claude의 판단과 무관하게 항상 실행됩니다. 이 점이 훅을 신뢰할 수 있는 자동화 수단으로 만드는 핵심입니다.

훅의 종류 3가지 — PreToolUse, PostToolUse, Stop

Claude Code는 세 가지 타입의 훅을 제공합니다. 각각 실행 시점이 다릅니다.

훅 타입실행 시점주요 용도
PreToolUse도구 실행 전위험 명령어 차단, 입력 검증
PostToolUse도구 실행 후자동 포맷팅, 린트, 테스트 실행
Stop세션 종료 시최종 빌드 검증, 보고서 생성

PreToolUse는 Claude가 어떤 도구(파일 쓰기, 명령어 실행 등)를 사용하기 전에 실행됩니다. 종료 코드 2를 반환하면 해당 도구 실행을 차단할 수 있어 안전장치로 활용하기 좋습니다.

PostToolUse는 도구 실행이 완료된 후 실행됩니다. 파일 저장 후 자동 포맷팅, 코드 린트, 타입 검사 등에 이상적입니다.

Stop은 Claude가 세션을 종료할 때 실행됩니다. 프로젝트 빌드 최종 검증, 테스트 전체 실행, 변경 사항 요약 생성 등에 활용합니다.

훅 설정 방법 — settings.json 구조

훅은 ~/.claude/settings.json 파일에서 설정합니다. 프로젝트별로 다르게 설정하고 싶다면 프로젝트 루트의 .claude/settings.json을 사용합니다. 프로젝트 설정이 글로벌 설정보다 우선합니다.

기본 구조는 다음과 같습니다:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "command": "명령어",
        "description": "훅 설명"
      }
    ],
    "PreToolUse": [
      {
        "matcher": "Bash",
        "command": "검증 명령어",
        "description": "훅 설명"
      }
    ],
    "Stop": [
      {
        "command": "빌드 명령어",
        "description": "훅 설명"
      }
    ]
  }
}

matcher는 정규식 패턴으로, 어떤 도구에 훅을 적용할지 지정합니다. Write|Edit는 파일 쓰기와 편집 도구 모두에 적용하며, Bash는 터미널 명령어 실행에, .*는 모든 도구에 적용합니다.

훅 명령어는 환경변수 $FILE_PATH로 현재 처리 중인 파일 경로를 받을 수 있습니다. 이를 활용하면 해당 파일에만 린트나 포맷팅을 적용하는 효율적인 훅을 만들 수 있습니다.

PreToolUse 훅 실전 활용 — 파일 저장 전 린트, 위험 명령어 차단

활용 1: 대용량 파일 저장 차단

800줄을 넘는 파일은 저장하지 못하도록 차단하는 훅입니다. 코딩 스타일 규칙을 자동으로 강제합니다.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write",
        "command": "node -e \"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const c=i.tool_input?.content||'';const lines=c.split('\\n').length;if(lines>800){console.error('[훅] 차단: 파일이 800줄을 초과합니다 ('+lines+'줄)');process.exit(2)}console.log(d)})\"",
        "description": "800줄 초과 파일 저장 차단"
      }
    ]
  }
}

활용 2: 위험 명령어 차단

rm -rf, git push --force 같은 위험한 명령어가 실행되기 전에 경고하고 차단합니다.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "command": "bash -c 'read input; echo \"$input\" | python3 -c \"import sys,json; d=json.load(sys.stdin); cmd=d.get(\\\"tool_input\\\",{}).get(\\\"command\\\",\\\"\\\"); dangerous=[\\\"rm -rf\\\",\\\"git push --force\\\",\\\"DROP TABLE\\\"]; [sys.exit(2) or print(\\\"[훅] 위험 명령어 차단:\\\",p) for p in dangerous if p in cmd]; print(json.dumps(d))\"'",
        "description": "위험한 명령어 실행 차단"
      }
    ]
  }
}

PostToolUse 훅 실전 활용 — 자동 포맷팅, 테스트 실행

활용 3: 파일 저장 후 자동 Prettier 포맷팅

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "command": "pnpm prettier --write \"$FILE_PATH\" 2>/dev/null || true",
        "description": "파일 저장 후 자동 포맷팅"
      }
    ]
  }
}

활용 4: TypeScript 파일 편집 후 타입 검사

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "command": "if [[ \"$FILE_PATH\" == *.ts || \"$FILE_PATH\" == *.tsx ]]; then pnpm tsc --noEmit --pretty false 2>&1 | tail -20; fi",
        "description": "TypeScript 파일 편집 후 타입 검사"
      }
    ]
  }
}

활용 5: ESLint 자동 실행 및 수정

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "command": "pnpm eslint --fix \"$FILE_PATH\" 2>/dev/null || true",
        "description": "ESLint 자동 수정"
      }
    ]
  }
}

활용 6: Python 파일 black 포맷팅

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "command": "if [[ \"$FILE_PATH\" == *.py ]]; then black \"$FILE_PATH\" 2>/dev/null; fi",
        "description": "Python 파일 black 포맷팅"
      }
    ]
  }
}

Stop 훅 활용 — 세션 종료 시 빌드 검증

Stop 훅은 Claude가 작업을 완료하고 세션을 종료할 때 실행됩니다. 최종 빌드가 성공하는지 확인하는 용도로 가장 많이 씁니다.

활용 7: 세션 종료 시 프로덕션 빌드 검증

{
  "hooks": {
    "Stop": [
      {
        "command": "pnpm build 2>&1 | tail -30",
        "description": "세션 종료 시 프로덕션 빌드 검증"
      }
    ]
  }
}

활용 8: 테스트 전체 실행

{
  "hooks": {
    "Stop": [
      {
        "command": "pnpm test --passWithNoTests 2>&1 | tail -40",
        "description": "세션 종료 시 전체 테스트 실행"
      }
    ]
  }
}

훅 10가지 실전 예제 코드

예제 9: CSS/SCSS 파일 Stylelint 자동 실행

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "command": "if [[ \"$FILE_PATH\" == *.css || \"$FILE_PATH\" == *.scss ]]; then pnpm stylelint --fix \"$FILE_PATH\" 2>/dev/null || true; fi",
        "description": "CSS/SCSS 파일 Stylelint 자동 실행"
      }
    ]
  }
}

예제 10: 완전한 프론트엔드 훅 설정 (포맷 → 린트 → 타입 검사)

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "command": "pnpm prettier --write \"$FILE_PATH\" 2>/dev/null || true",
        "description": "1단계: 자동 포맷팅"
      },
      {
        "matcher": "Write|Edit",
        "command": "pnpm eslint --fix \"$FILE_PATH\" 2>/dev/null || true",
        "description": "2단계: ESLint 자동 수정"
      },
      {
        "matcher": "Write|Edit",
        "command": "if [[ \"$FILE_PATH\" == *.ts || \"$FILE_PATH\" == *.tsx ]]; then pnpm tsc --noEmit --pretty false 2>&1 | head -20; fi",
        "description": "3단계: TypeScript 타입 검사"
      }
    ],
    "PreToolUse": [
      {
        "matcher": "Write",
        "command": "node -e \"let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{const i=JSON.parse(d);const c=i.tool_input?.content||'';if(c.split('\\n').length>800){process.stderr.write('[훅] 800줄 초과 차단\\n');process.exit(2)}process.stdout.write(d)})\"",
        "description": "파일 크기 제한"
      }
    ],
    "Stop": [
      {
        "command": "pnpm build 2>&1 | tail -20",
        "description": "최종 빌드 검증"
      }
    ]
  }
}

팁: 훅 명령어가 실패해도 Claude Code의 기본 동작에는 영향을 주지 않습니다 (PreToolUse 훅에서 exit 2를 반환하는 경우 제외). 따라서 훅에 || true를 붙여 에러를 무시하는 것이 좋은 습관입니다.

훅 작성 시 주의사항

훅을 작성할 때 몇 가지 중요한 점을 기억해야 합니다.

  1. 무한 루프 주의: PostToolUse 훅에서 파일을 수정하면 다시 PostToolUse 훅이 트리거될 수 있습니다. Prettier나 ESLint처럼 같은 파일을 두 번 처리해도 결과가 같은 도구만 사용하세요.
  2. 성능 고려: 모든 파일 저장에 무거운 타입 검사를 실행하면 속도가 느려집니다. 파일 확장자로 필터링하거나, Stop 훅으로 옮기는 것을 고려하세요.
  3. 경로 처리: $FILE_PATH는 절대 경로로 전달됩니다. 상대 경로가 필요하다면 변환이 필요합니다.
  4. 에러 출력: 훅에서 출력되는 내용은 Claude에게 컨텍스트로 전달될 수 있습니다. 중요한 에러는 stderr로 출력하는 것이 좋습니다.
  5. 환경 변수: 훅 명령어는 Claude Code가 실행된 셸 환경을 상속합니다. PATH, 가상환경 등이 올바르게 설정되어 있는지 확인하세요.

훅 시스템을 잘 활용하면 Claude Code가 단순한 AI 어시스턴트를 넘어 팀의 코딩 표준을 자동으로 강제하는 시스템으로 변합니다. 한 번 설정하면 모든 팀원에게 일관된 코드 품질을 보장할 수 있습니다.

관련 리소스