배포하고 나서 lint 오류를 발견한 적 있다면, 이 글이 그 반복을 끊는다. Claude Code의 Hooks 파이프라인으로 커밋·배포 전 다단계 검증을 직렬로 연결하는 방법과, 실무에서 맞닥뜨린 타임아웃·fail-open 함정까지 정리한다.
한눈에 보는 답
- 게이트 순서가 속도를 결정한다. lint(가장 빠름) → secret-scan → type-check(가장 무거움) 순으로 쌓아야 전체 파이프라인 소요 시간이 최소화된다.
- non-zero 반환 즉시 체인이 끊긴다. 하나의 훅이 실패하면 이후 훅은 실행되지 않는다. 이 동작을 믿으려면 반환 코드를 반드시 명시해야 한다.
- 네트워크 의존 검증은 PostToolUse로 분리해야 한다. PreToolUse 훅에 외부 API 호출을 넣으면 hang 위험이 생긴다.
1. 왜 지금 이걸 봐야 하나
팀에서 시크릿 키가 커밋됐는지 다시 확인하는 코드 리뷰, CI에서 터진 타입 오류 때문에 기다리는 PR 머지 대기 시간, 배포 후 발견되는 lint 경고. 이 세 가지가 반복된다면 문제는 개인의 실수가 아니라 구조다.
검증이 커밋 이후에 일어나는 구조이기 때문이다. CI는 원격에서, 리뷰어는 PR이 열린 뒤에 확인한다. 오류를 가장 늦게 발견할수록 수정 비용이 커진다. 로컬에서, 커밋 버튼을 누르기 전에 게이트를 세우면 이 비용 전체를 없앨 수 있다.
Claude Code의 Hooks는 바로 이 지점에 개입한다. AI가 도구를 실행하기 전(PreToolUse) 또는 후(PostToolUse)에 커스텀 스크립트를 끼워 넣는 구조다. 단순한 자동 실행이 아니라, 각 훅을 순서 있는 게이트 체인으로 묶으면 공항 보안검색대처럼 동작한다. X레이, 금속탐지, 신원조회가 순서대로 통과돼야 탑승구에 들어가듯, 훅 하나가 실패하면 다음 단계로 넘어가지 않는다.
2. 핵심 아이디어
훅 파이프라인의 본질은 직렬 게이트 체인이다.
.claude/settings.json의 hooks 배열에 명령어를 순서대로 선언하면 Claude Code는 위에서 아래로 직렬 실행한다. 하나가 non-zero를 반환하는 순간 전체 체인이 멈춘다. 이 단순한 구조가 커밋 전 다단계 검증의 토대가 된다.
세 종류의 게이트를 어떻게 배치하느냐에 따라 성격이 달라진다.
| 게이트 | 도구 | 평균 실행 시간 | 배치 이유 |
|---|---|---|---|
| lint-check | eslint --max-warnings 0 |
약 0.8초 | 가장 빠름. 앞에 두면 이후 무거운 게이트 실행 빈도가 낮아짐 |
| secret-scan | gitleaks detect --no-banner |
약 1.5초 | 중간 속도. 보안 차단은 타입 체크보다 먼저 해야 함 |
| type-check | tsc --noEmit |
약 4~8초 | 가장 무거움. 앞의 두 게이트가 통과된 코드에만 돌려야 낭비가 없음 |
lint 오류가 있는 상태에서 tsc를 풀로 돌리는 건 구조적 낭비다. 가장 빠른 게이트를 앞에 두는 것만으로도 파이프라인 전체의 평균 실행 시간이 줄어든다.
3. 바로 따라하는 방법
설정 파일 구성
.claude/settings.json에 hooks 블록을 추가한다. Bash 도구 실행 전에 세 개의 게이트를 직렬로 묶는 구조다.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "bash .claude/hooks/lint-check.sh" },
{ "type": "command", "command": "bash .claude/hooks/secret-scan.sh" },
{ "type": "command", "command": "bash .claude/hooks/type-check.sh" }
]
}
]
}
}
게이트 1 — lint-check.sh
#!/bin/bash
# lint-check.sh — PreToolUse 게이트 1번
npx eslint . --max-warnings 0
EXIT_CODE=$?
echo "[lint-check] 반환 코드: $EXIT_CODE"
if [ $EXIT_CODE -ne 0 ]; then
echo "[GATE BLOCKED] lint 오류를 수정한 뒤 재시도하세요."
exit 1
fi
exit 0
게이트 2 — secret-scan.sh
#!/bin/bash
# secret-scan.sh — PreToolUse 게이트 2번
timeout 15 gitleaks detect --no-banner
EXIT_CODE=$?
echo "[secret-scan] 반환 코드: $EXIT_CODE"
if [ $EXIT_CODE -ne 0 ]; then
echo "[GATE BLOCKED] 시크릿 키 누출 가능성이 탐지됐습니다."
echo " false-positive라면 .gitleaks.toml에 예외를 추가하세요."
exit 1
fi
exit 0
false-positive가 자주 발생하는 경우 .gitleaks.toml에서 특정 파일이나 패턴을 예외 처리할 수 있다.
# .gitleaks.toml
[[allowlist]]
description = "테스트용 더미 키"
paths = ["tests/fixtures/.*"]
게이트 3 — type-check.sh
#!/bin/bash
# type-check.sh — PreToolUse 게이트 3번
timeout 30 npx tsc --noEmit
EXIT_CODE=$?
echo "[type-check] 반환 코드: $EXIT_CODE"
if [ $EXIT_CODE -ne 0 ]; then
echo "[GATE BLOCKED] TypeScript 타입 오류를 수정한 뒤 재시도하세요."
exit 1
fi
exit 0
검증 방법
설치 후 의도적으로 lint 오류가 있는 파일을 만들고 Claude Code에서 Bash 도구를 실행해본다.
# 의도적 lint 오류 생성
echo "var x = 1" >> test-gate.js
# Claude Code 내에서 Bash 실행 시 게이트 1번에서 차단되는지 확인
# 예상 출력:
# [lint-check] 반환 코드: 1
# [GATE BLOCKED] lint 오류를 수정한 뒤 재시도하세요.
게이트 2, 3 순서로도 동일하게 검증한다. 각 스크립트를 직접 실행했을 때 exit 1이 반환되는지 확인하는 것이 가장 확실하다.
bash .claude/hooks/lint-check.sh; echo "최종 반환 코드: $?"
4. 운영할 때 조심할 점
타임아웃 함정
외부 네트워크 호출을 훅 안에 넣으면 타임아웃이 발생할 수 있다. Claude Code Hooks의 기본 타임아웃은 60초인데, 외부 API 응답이 늦으면 훅 자체가 hang 상태로 빠진다. 위 예시처럼 timeout 15 <명령어> 래퍼로 강제 종료 시간을 명시하는 것이 안전하다.
네트워크 의존 검증은 PreToolUse 대신 PostToolUse로 분리하는 것이 낫다. 실행을 차단하는 게 목적이 아니라 결과를 기록하거나 알리는 게 목적이라면 PostToolUse가 적합하다.
fail-open 함정
훅 스크립트가 파싱 오류나 경로 문제로 실행되지 않으면 Claude Code는 기본적으로 통과시킨다. 훅이 있으나 마나한 장식이 되는 상황이다. 각 스크립트 안에 echo $?로 반환 코드를 명시적으로 출력해두면, 훅이 작동하는지 여부를 로그로 확인할 수 있다. 처음 설정할 때 한 번은 반드시 의도적 오류로 차단 동작을 직접 검증해야 한다.
환경별 차이
| 환경 | 주의사항 |
|---|---|
| macOS | timeout 명령어가 기본 설치 안 됨. brew install coreutils 후 gtimeout 사용 또는 brew link --force coreutils로 timeout 대체 |
| Linux | timeout 기본 내장. 별도 설치 불필요 |
| Docker | 컨테이너 내 gitleaks 설치 여부 확인 필요. 이미지 빌드 시 포함해야 함 |
| CI 환경 | PreToolUse 훅은 로컬 Claude Code 전용. CI 파이프라인과 중복 설정해도 각자의 역할이 있음 |
성능 모니터링
게이트를 추가할수록 전체 실행 시간이 늘어난다. 각 스크립트 상단에 시작 시간을 찍어두면 어느 게이트가 병목인지 파악할 수 있다.
#!/bin/bash
START=$(date +%s%N)
# ... 검증 로직 ...
END=$(date +%s%N)
echo "[type-check] 실행 시간: $(( (END - START) / 1000000 ))ms"
근거와 검증 기준
| 확인할 것 | 기준 |
|---|---|
| 훅 실행 여부 | 각 스크립트 실행 시 반환 코드($?)가 로그에 찍히는지 직접 확인 |
| 게이트 차단 동작 | 의도적 오류 파일 생성 후 Bash 도구 실행 → 첫 번째 게이트에서 차단되는지 확인 |
| 타임아웃 설정 | timeout N <cmd> 래퍼가 없는 훅은 hang 위험. 외부 호출이 있는 훅은 반드시 래퍼 추가 |
| fail-open 여부 | 스크립트 경로가 틀렸을 때 훅이 통과되는지 확인. 경로 오타는 가장 흔한 실수 |
| CI 빌드 실패율 | 파이프라인 도입 전후 CI 빌드 실패 횟수를 같은 기간으로 비교 |
| 공식 문서 | Claude Code Hooks 공식 문서(docs.anthropic.com)에서 타임아웃, 반환 코드 동작 확인 |
자주 묻는 질문
Hooks 사전 검증 체인은 언제 쓰는 게 좋을까?
팀 전체가 동일한 코드 품질 기준을 따라야 하거나, CI에서 반복적으로 같은 종류의 오류가 터지는 상황에서 효과가 크다. 혼자 작업하는 프로젝트라도 시크릿 스캔 게이트 하나만 추가해도 실수로 인한 키 누출을 사전에 막을 수 있다.
적용하기 전에 무엇을 먼저 확인해야 할까?
각 게이트에 사용하는 도구(eslint, gitleaks, tsc)가 로컬 환경에 설치돼 있는지, 그리고 훅 스크립트 파일 경로가 .claude/settings.json에 선언한 경로와 정확히 일치하는지 확인한다. 경로 오타 하나로 fail-open 상태가 되는 경우가 가장 흔하다.
게이트가 제대로 작동하는지 어떻게 검증할까?
의도적으로 오류를 만들어서 차단되는지 확인하는 것이 유일하게 확실한 방법이다. lint 오류 파일을 하나 만들고, Claude Code 안에서 Bash 도구를 실행했을 때 게이트 1번에서 차단 메시지가 뜨는지 본다. 통과되면 훅이 작동하지 않고 있는 것이다.
마무리
빠른 게이트를 앞에, 무거운 게이트를 뒤에 두고, 반환 코드를 명시하고, 타임아웃 래퍼를 빠뜨리지 않으면 Claude Code Hooks 파이프라인은 커밋 전 자동 게이트키퍼로 완성된다. .claude/settings.json에 세 줄을 추가하는 것부터 시작하면 된다.
다음 글에서는 PostToolUse 훅을 활용해 배포 결과를 Slack으로 자동 전송하고, 훅 실행 로그를 구조화해서 파이프라인 히스토리를 관리하는 방법을 다룬다.
🐦 X에서 더 빠르게: @baegseungh7061
📚 이 시리즈 더 보기: Code 실전
💌 새 글 알림: X 팔로우 또는 블로그 RSS 구독
'Code 실전' 카테고리의 다른 글
| 요청 컨텍스트에 따라 스킬 묶음을 런타임에 교체하는 Claude Code 동적 로딩 구조 (0) | 2026.05.30 |
|---|---|
| CLAUDE.md로 Claude Code 프로젝트 기억 설계하는 법 (0) | 2026.05.29 |
| Claude Code가 요청보다 먼저 스킬을 준비하는 방법 — Skills 예측 초기화 구조 완전 해설 (0) | 2026.05.28 |
| Claude Code Sub-agent 병렬 실행으로 빌드 시간 61% 줄이는 법 (0) | 2026.05.25 |
| 에이전트 인스턴스가 늘어날수록 상태가 갈라지는 문제, Redis 브로드캐스트로 구조적으로 막는 법 (0) | 2026.05.22 |