Claude Code Hooks로 워크플로우 자동화하기
Claude Code를 쓰다 보면 "이건 매번 자동으로 해줬으면 좋겠는데"라는 생각이 드는 순간이 있다. 파일을 수정할 때마다 Prettier를 돌린다든지, 실수로 .env 파일을 건드리지 않게 막는다든지. 이런 걸 매번 말로 요청하는 건 비효율적이다.
Claude Code의 hooks 기능을 사용하면 이런 작업들을 자동화할 수 있다.
Hook이란
Hook은 Claude Code의 특정 이벤트가 발생할 때 자동으로 실행되는 셸 명령어다. 파일을 편집하기 전, 편집한 후, 세션이 시작될 때 등 다양한 시점에 원하는 스크립트를 끼워넣을 수 있다.
핵심은 결정론적 제어 다. LLM이 "알아서 잘 해주길 기대"하는 게 아니라, 특정 규칙을 100% 강제할 수 있다는 뜻이다. "Prettier 돌려줘"라고 매번 말할 필요 없이, 파일이 수정되면 무조건 Prettier가 실행되게 만들 수 있다.
언제 실행되는가
Hook이 동작하는 주요 시점들이 있다.
PreToolUse 는 Claude Code가 도구를 실행하기 직전에 동작한다. 여기서 특정 작업을 차단할 수 있다. 예를 들어 .env 파일 수정을 막거나, rm -rf 같은 위험한 명령어를 차단하는 식이다.
PostToolUse 는 도구 실행이 완료된 직후에 동작한다. 파일 편집 후 포맷팅을 자동으로 돌리거나, 린트 에러가 있는지 체크하는 용도로 쓸 수 있다.
SessionStart 는 Claude Code 세션이 시작될 때 한 번 실행된다. 프로젝트별 컨텍스트를 자동으로 주입하거나, 개발 환경 상태를 체크하는 데 유용하다.
Notification 은 Claude Code가 사용자에게 알림을 보낼 때 실행된다. 데스크톱 알림을 띄우거나 슬랙으로 메시지를 보내는 등의 작업을 연결할 수 있다.
설정 방법
가장 쉬운 방법은 Claude Code에서 /hooks 명령어를 입력하는 것이다. 대화형 메뉴가 나오고, 원하는 이벤트를 선택해서 명령어를 등록할 수 있다.
직접 설정 파일을 수정하고 싶다면 .claude/settings.json에 작성하면 된다. 프로젝트 루트에 이 파일을 만들어두면 해당 프로젝트에서만 적용된다. 전역으로 적용하고 싶다면 ~/.claude/settings.json에 작성한다.
실용적인 예시들
파일 편집 후 자동 포맷팅
Claude Code가 파일을 수정할 때마다 Prettier를 자동으로 실행하는 설정이다.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "jq -r '.tool_input.file_path' | xargs npx prettier --write 2>/dev/null || true"
}
]
}
]
}
}
matcher에 Edit|Write를 지정해서 파일 편집이나 생성 도구가 사용됐을 때만 실행되게 한다. Hook은 stdin으로 JSON 데이터를 받는데, 여기서 jq로 파일 경로를 추출해서 Prettier에 넘긴다.
이렇게 해두면 Claude Code가 어떤 파일을 수정하든 자동으로 코드 스타일이 맞춰진다.
위험한 명령어 차단
rm -rf나 drop table 같은 위험한 명령어를 실행하려고 할 때 차단하는 설정이다.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash -c 'INPUT=$(cat); CMD=$(echo \"$INPUT\" | jq -r .tool_input.command); if [[ \"$CMD\" == *\"rm -rf\"* ]] || [[ \"$CMD\" == *\"drop table\"* ]]; then echo \"위험한 명령어입니다\" >&2; exit 2; fi'"
}
]
}
]
}
}
Exit code가 중요하다. exit 0 은 "진행해도 됨", exit 2 는 "차단"을 의미한다. 차단할 때 stderr로 메시지를 출력하면 Claude Code가 그 이유를 알 수 있다.
보호된 파일 편집 차단
.env, package-lock.json, .git 폴더 등 건드리면 안 되는 파일들을 보호하는 설정이다.
먼저 .claude/hooks/protect-files.sh 파일을 만든다:
#!/bin/bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
PROTECTED_PATTERNS=(".env" "package-lock.json" ".git/")
for pattern in "${PROTECTED_PATTERNS[@]}"; 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": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
}
]
}
]
}
}
$CLAUDE_PROJECT_DIR 환경 변수를 사용하면 프로젝트 루트 경로를 참조할 수 있다.
세션 시작 시 컨텍스트 주입
세션이 시작될 때 프로젝트별 주의사항을 자동으로 알려주는 설정이다.
{
"hooks": {
"SessionStart": [
{
"matcher": "startup",
"hooks": [
{
"type": "command",
"command": "echo '이 프로젝트에서는 npm 대신 bun을 사용합니다. PR 전에 bun test를 실행하세요.'"
}
]
}
]
}
}
Hook의 stdout 출력은 Claude Code에게 전달된다. 이렇게 해두면 매번 "bun 써야 해"라고 말할 필요가 없다.
Hook이 받는 데이터
모든 Hook은 stdin으로 JSON 형식의 컨텍스트 데이터를 받는다.
{
"session_id": "abc123",
"cwd": "/Users/user/myproject",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "npm test"
}
}
jq를 사용하면 이 데이터에서 필요한 정보를 쉽게 추출할 수 있다. jq가 설치되어 있지 않다면 brew install jq로 설치하면 된다.
디버깅
Hook이 예상대로 동작하지 않을 때는 claude --debug 옵션으로 실행하면 Hook 실행 과정을 자세히 볼 수 있다.
정리
Hook의 핵심은 "LLM이 잘 해주길 기대"하는 게 아니라 규칙을 강제 한다는 점이다. 포맷팅은 자동으로, 위험한 작업은 차단으로, 중요한 컨텍스트는 주입으로. 한번 설정해두면 매번 말로 요청하지 않아도 되니까 워크플로우가 훨씬 깔끔해진다.
연관 포스트
- Claude Code 개발 가이드 - Claude Code 활용법 종합 가이드
- Claude Code로 개발할 때 알아두면 좋은 팁들 - 서버 로그 관리, nodemon 설정 등