배포 직전 손이 떨리는 경험, 한 번이라도 있다면 이 글이 그 불안을 없애줄 수 있다. staging에서 멀쩡하던 게 prod에서 터지는 이유는 대부분 사람이 판단했기 때문이다. Claude Code의 훅 레이어를 쓰면 그 판단을 구조로 옮길 수 있다.
1. 왜 지금 이걸 봐야 하나
배포 파이프라인에는 항상 같은 구멍이 있다. CI/CD가 통과해도, 코드 리뷰가 끝나도, 실제로 명령을 실행하는 순간만큼은 사람 손이 들어간다. DROP TABLE, rm -rf, 외부 API 직접 호출 — 이런 것들이 staging에서는 아무 문제 없다가 prod에서 롤백을 부른다.
문제의 구조는 단순하다. 환경을 확인하는 시점이 너무 늦다. 명령을 입력하고 나서야 "아, 여기가 prod였지"를 깨닫는다. 그때는 이미 늦었다.
Claude Code의 PreToolUse 훅은 이 구조를 바꾼다. 도구가 실행되기 직전에 끼어들어서, 환경을 먼저 확인하고 통과·차단을 결정한다. 사람이 판단하기 전에 구조가 먼저 개입하는 방식이다.
2. 핵심 아이디어
PreToolUse 훅에서 DEPLOY_ENV를 읽고, 명령이 실행되기 전에 통과·차단을 결정한다.
훅은 공항 보안 검색대다. 비행기에 타기 전에 짐을 검사한다. 탑승 후에 검사하면 의미가 없다. PostToolUse에 안전 로직을 붙이면 이미 실행된 다음이다. 훅의 위치가 전부다.
환경별로 금지 명령의 종류가 다르다는 점도 중요하다.
| 환경 | 차단 대상 | 이유 |
|---|---|---|
| production | DROP, TRUNCATE, DELETE FROM, rm -rf | 되돌릴 수 없는 파괴 명령 |
| staging | 외부 도메인 curl/wget | 실서비스 API 오염 방지 |
| development | 없음 | 자유롭게 실험 |
3. 바로 따라하는 방법
훅 등록
먼저 .claude/settings.json에 훅 엔트리를 추가한다. Bash와 Write 두 도구 모두 게이팅해야 한다. Bash만 걸면 Write 도구로 설정 파일을 직접 덮어쓰는 경우가 빠져나간다.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash ~/.claude/hooks/env-gate.sh"
}
]
},
{
"matcher": "Write",
"hooks": [
{
"type": "command",
"command": "bash ~/.claude/hooks/write-gate.sh"
}
]
}
]
}
}
env-gate.sh 작성
~/.claude/hooks/ 디렉터리를 만들고 아래 스크립트를 저장한다.
#!/usr/bin/env bash
# env-gate.sh — 환경별 분기 안전망
ENV="${DEPLOY_ENV:-development}"
TOOL_INPUT=$(cat)
if echo "$TOOL_INPUT" | grep -q '"command"'; then
CMD=$(echo "$TOOL_INPUT" | python3 -c \
"import sys,json; print(json.load(sys.stdin).get('command',''))")
fi
# production: 파괴적 명령 차단
if [ "$ENV" = "production" ]; then
if echo "$CMD" | grep -qiE '(DROP|TRUNCATE|DELETE FROM|rm -rf)'; then
echo '{"decision":"block","reason":"prod 환경에서 파괴적 명령은 차단됩니다"}'
exit 0
fi
fi
# staging: 외부 도메인 API 호출 차단
if [ "$ENV" = "staging" ]; then
if echo "$CMD" | grep -qE 'curl|wget|fetch' \
&& ! echo "$CMD" | grep -q 'staging\.internal'; then
echo '{"decision":"block","reason":"staging은 내부 도메인만 허용합니다"}'
exit 0
fi
fi
echo '{"decision":"approve"}'
chmod +x ~/.claude/hooks/env-gate.sh
검증
# staging 환경으로 외부 curl 시도
DEPLOY_ENV=staging claude -p "curl https://api.stripe.com/v1/charges 실행해줘"
# → 차단 메시지 출력 확인
# production 환경으로 DROP 시도
DEPLOY_ENV=production claude -p "DROP TABLE users 실행해줘"
# → 차단 메시지 출력 확인
실측 기준으로 훅 응답 지연은 평균 18ms였다. 체감상 없는 수준이다.
PostToolUse: prod 실행 알림 붙이기
차단과 별개로, prod에서 뭔가 실행됐을 때 Slack으로 알림을 보내면 "내가 모르는 사이에 prod에서 뭔가 실행됐다"는 불안이 없어진다.
#!/usr/bin/env bash
# notify-prod.sh — prod 실행 완료 알림
ENV="${DEPLOY_ENV:-development}"
if [ "$ENV" = "production" ]; then
RESULT=$(cat)
TOOL_NAME=$(echo "$RESULT" | python3 -c \
'import sys,json; d=json.load(sys.stdin); print(d.get("tool_name",""))')
curl -s -X POST "$SLACK_WEBHOOK_URL" \
-H 'Content-Type: application/json' \
-d "{\"text\":\"[prod] 훅 실행 완료: ${TOOL_NAME}\"}" > /dev/null
fi
4. 운영할 때 조심할 점
PostToolUse에 안전 로직을 붙이는 실수가 가장 흔하다. 처음 이 구조를 만들 때 PostToolUse에 차단 로직을 넣었다가 전혀 동작하지 않아서 시간을 낭비했다. PostToolUse는 실행이 끝난 뒤에 호출된다. 차단은 반드시 PreToolUse에서 해야 한다.
Write 도구를 빼먹는 실수도 있다. Bash만 게이팅하면 Claude가 Write 도구를 써서 prod.env 파일을 직접 덮어쓰는 경우가 뚫린다. write-gate.sh는 파일 경로 기준으로 prod 설정 파일 패턴(*.prod.*, production.*)을 차단하면 된다.
환경 변수 주입 타이밍도 확인해야 한다. DEPLOY_ENV가 Claude Code 프로세스 시작 시점에 설정되어 있어야 훅에서 읽힌다. CI/CD 파이프라인에서는 export DEPLOY_ENV=production을 Claude 실행 직전에 넣는다.
Mac/Linux 환경 모두 동일하게 동작하지만, Docker 컨테이너 안에서 쓴다면 ~/.claude/hooks/ 경로를 볼륨 마운트로 유지해야 컨테이너 재시작 후에도 훅이 살아있다.
마무리
훅은 안전망이 아니라 교통 신호등이다. PreToolUse에서 막지 않으면 PostToolUse는 이미 늦다. DEPLOY_ENV를 기준으로 Bash와 Write 모두 게이팅하면, 사람의 실수가 prod에 닿기 전에 구조가 먼저 막아낸다. 3개월 무롤백이 그 결과였다.
다음 글에서는 훅 레이어에 승인 플로우(사람이 직접 y/n을 입력하는 인터랙티브 게이트)를 붙이는 방법을 다룬다.
🐦 X에서 더 빠르게: @baegseungh7061
📚 이 시리즈 더 보기: Code 실전
💌 새 글 알림: X 팔로우 또는 블로그 RSS 구독
'Code 실전' 카테고리의 다른 글
| 에이전트 인스턴스가 늘어날수록 상태가 갈라지는 문제, Redis 브로드캐스트로 구조적으로 막는 법 (0) | 2026.05.22 |
|---|---|
| 코드 배포 없이 에이전트 행동을 즉시 바꾸는 런타임 토글 설계법 (0) | 2026.05.21 |
| MCP 요청 위변조를 HMAC 서명으로 원천 차단하는 법 (0) | 2026.05.15 |
| Claude Code 훅을 이벤트 버스로 바꾸면 응답이 26배 빨라진다 (0) | 2026.05.13 |
| 에이전트 실행 순서, DAG와 위상 정렬로 코드에 새기기 (0) | 2026.05.12 |