Skip to main content

서브에이전트가 혼자 일하는 사람에게 역할 분리를 가져다줬다면, Hooks는 그 역할이 언제 자동으로 개입할지를 정해 준다. 비유하자면 서브에이전트가 팀원이라면, Hooks는 자동문과 방화문이다. 평소에는 조용히 있다가, 누가 들어오거나 위험한 일이 벌어질 때만 딱 움직인다.

솔로 빌더에게 이게 왜 중요할까. 혼자 개발하면 속도는 빠르다. 대신 실수의 반경도 넓다. 내가 잘못 친 명령 하나, 에이전트가 과감하게 실행한 수정 하나가 반나절을 날릴 수 있다. 그래서 빠르게 만드는 사람일수록 더 많은 자유가 아니라, 더 좋은 자동 안전망이 필요하다.

CLAUDE.md가 "이렇게 행동해 줘"라고 부탁하는 문서라면, Hooks는 "이 시점에는 이 검사를 무조건 해"라고 못 박는 장치다. 즉, 확률적인 시스템 위에 결정적인 레일을 까는 일이다.1

[3부] Hooks: 이벤트 기반 자동화

9.1 PreToolUse, PostToolUse, SubagentStop, Stop 이벤트

Claude Code의 훅은 세션 안에서 특정 이벤트가 발생할 때 자동으로 실행되는 명령, HTTP 호출, 또는 프롬프트 기반 검사다.1 이 장에서는 그중 솔로 빌더가 가장 자주 쓰게 될 네 가지부터 잡고 가자.

첫째는 PreToolUse다. 이름 그대로 도구가 실행되기 직전에 발동한다. Bash 명령을 막거나, 특정 파일에 대한 쓰기를 차단하거나, "이건 자동 승인해도 된다" 같은 규칙을 넣기에 가장 좋다. 쉽게 말해 브레이크 페달이다.

둘째는 PostToolUse다. 도구가 성공적으로 실행된 뒤에 발동한다. 파일을 수정한 다음 Prettier를 돌리거나, lint를 실행하거나, 짧은 테스트를 붙이는 데 잘 맞는다. 브레이크라기보다 자동 정렬 장치에 가깝다.

셋째는 SubagentStop이다. 서브에이전트가 자기 일을 끝냈을 때 발동한다. 앞 장에서 만든 PM, 구현자, 리뷰어 파이프라인을 떠올리면 이해가 쉽다. 리뷰어 서브에이전트가 끝날 때 요약을 남기거나, 구현 서브에이전트가 끝날 때 검증 스크립트를 붙이는 식이다.1

넷째는 Stop이다. 메인 에이전트가 한 턴의 응답을 마치고 멈추려 할 때 발동한다. 이 시점은 "정말 끝이라고 말해도 되나?"를 확인하는 마지막 체크포인트다. 작업 요약, 비용 기록, 최종 빌드 확인, 남은 리스크 알림을 넣기 좋다.2

핵심은 이 네 이벤트가 서로 경쟁하지 않는다는 점이다. 각자 맡는 시간이 다르다.

  • PreToolUse: 실행 전 안전장치

  • PostToolUse: 실행 후 품질 보정

  • SubagentStop: 하위 작업 종료 시점의 핸드오프 관리

  • Stop: 메인 작업 마감 직전의 최종 게이트

이 구조를 이해하면 훅 설계가 쉬워진다. 위험한 것을 막는 일은 PreToolUse에, 포맷팅과 자동 수정은 PostToolUse에, 파이프라인 연결은 SubagentStop에, 최종 확인은 Stop에 두면 된다. 한 훅이 모든 책임을 다 지려 들면 금방 복잡해진다.

실제 설정은 보통 .claude/settings.json 또는 ~/.claude/settings.json에 넣는다. 팀과 공유할 규칙은 프로젝트의 .claude/settings.json, 개인 취향은 홈 디렉터리 쪽 설정에 두는 편이 안정적이다.2

아주 얇은 골격은 이렇게 생긴다.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/guard-bash.sh"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/after-write.sh"
          }
        ]
      }
    ],
    "SubagentStop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/subagent-finish.sh"
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/final-check.sh"
          }
        ]
      }
    ]
  }
}

훅은 JSON 입력을 stdin으로 받는다. 그래서 스크립트는 보통 jq로 .tool_input.command나 .tool_input.file_path를 꺼내 확인한다.1 여기서 중요한 감각은 이것이다. 훅은 Claude의 "기분"에 따라 실행되는 것이 아니라, Claude Code 하네스가 호스트 환경에서 강제로 실행하는 코드다. 그래서 신뢰할 수 있다. 동시에 잘못 만들면 내 환경에 정확히 문제를 일으킨다. 칼은 요리도 하고 손도 벤다.

9.2 커밋 전 자동 린트, 테스트, 보안 검사

Hooks의 가장 실용적인 첫 사용처는 거창한 보안 정책이 아니다. 의외로 귀찮아서 자주 빼먹는 일이다. 포맷팅, 린트, 타입 체크, 짧은 테스트. 이런 것들은 중요하지만, 혼자 만들 때는 늘 "조금 있다 하지 뭐"가 된다.

문제는 솔로 빌더의 "조금 있다"가 대개 배포 직전이거나, 더 나쁘면 사용자 제보 이후라는 점이다. 그래서 훅의 첫 임무는 대단한 통제가 아니라 실수의 발견 시점을 앞당기는 것이다.

가장 운영하기 좋은 방식은 검사를 두 층으로 나누는 것이다.5

  • 마이크로 게이트: 파일 수정 직후 실행되는 가벼운 검사

  • 매크로 게이트: 작업 종료 직전에 실행되는 전체 확인

  • 핸드오프 게이트: 서브에이전트가 끝날 때 붙는 역할별 검증

예를 들어 PostToolUse에서 모든 파일 수정 뒤에 바로 전체 테스트를 돌리면 어떻게 될까. 몇 번은 멋져 보인다. 하지만 곧 세션이 느려지고, 사소한 수정에도 매번 팬이 돌기 시작한다. 그러면 사람은 자동화를 사랑하지 않는다. 자동화를 끄고 싶어진다.

그래서 PostToolUse에는 싸고, 빠르고, 수정 범위가 좁은 검사를 넣는 편이 좋다. 예를 들면 이런 식이다.

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | { read FILE; case \"$FILE\" in *.ts|*.tsx|*.js|*.jsx) npx prettier --write \"$FILE\" && npx eslint --fix \"$FILE\" ;; *.py) black \"$FILE\" ;; esac; }"
          }
        ]
      }
    ]
  }
}

이 패턴의 장점은 단순하다. Claude가 파일을 고치면, 바로 그 파일에 필요한 최소한의 위생 처리를 붙인다. 코드가 지저분해지는 시간을 거의 없애 준다. 마치 설거지를 하루 끝에 몰아서 하지 않고, 쓰자마자 물에 담가 두는 느낌이다.

그다음 Stop에서는 더 무거운 검사를 맡긴다. 예를 들어 pnpm build, pnpm test --passWithNoTests, 보안 스캐너, 필수 환경 변수 체크 같은 것들이다. 이 단계의 목적은 "파일 하나가 예쁘냐"가 아니라, 이번 턴에서 만들어진 결과물이 실제로 서 있느냐다.

여기서 중요한 운영 원칙이 있다.

  • PostToolUse에는 자동 수정 가능한 검사 위주로 둔다.

  • Stop에는 전체 상태를 확인하는 검사를 둔다.

  • 실패 메시지는 사람이 아니라 Claude가 다시 고칠 수 있게 구체적으로 남긴다.

예를 들어 "테스트 실패"보다는 "auth.spec.ts의 rejects invalid token 케이스 실패"가 훨씬 좋다. 에이전트는 애매한 꾸중보다 구체적인 에러 로그를 더 잘 고친다.

보안 검사도 같은 원리다. 모든 파일 수정 뒤에 무거운 SAST를 돌릴 필요는 없다. 대신 package.json, 인증 로직, SQL이 있는 모듈, 환경 변수 로딩 코드처럼 사고가 비싼 경로에만 집중하는 편이 현실적이다. Hooks는 모든 것을 다 감시하려고 넣는 게 아니라, 가장 자주 깨지는 지점부터 레일을 까는 일이다.

또 하나, PostToolUse 훅이 파일을 다시 수정하면 그 수정이 또 훅을 부를 수 있다. 그래서 포맷터와 린터는 가능하면 idempotent, 즉 두 번 돌려도 결과가 같아지는 도구를 쓰는 것이 좋다. Prettier나 Black이 사랑받는 이유가 여기 있다. 괜히 훅이 자기 꼬리를 쫓게 만들지 마라.

9.3 위험한 명령 차단하기: rm -rf, force push, 민감 파일 접근

솔로 빌더가 Hooks를 쓰는 가장 강력한 이유는 여기 있다. AI가 잘못할 수 있다는 사실을 전제로 안전장치를 건다.

많은 사람이 처음에는 CLAUDE.md에 이렇게 쓴다. "절대 파일을 삭제하지 마." "main 브랜치에 force push 하지 마." ".env는 건드리지 마." 물론 쓰는 게 좋다. 하지만 거기서 끝내면 안 된다. 그건 헬멧이지 에어백이 아니다.

위험한 명령 차단의 기본 패턴은 PreToolUse + Bash matcher다.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/guard-bash.sh"
          }
        ]
      }
    ]
  }
}

그리고 guard-bash.sh는 대략 이런 식으로 만든다.

#!/bin/bash
INPUT=$(cat)
CMD=$(echo "$INPUT" | jq -r '.tool_input.command // ""')

if echo "$CMD" | grep -qE 'b(rm|unlink|shred|del)b|git reset --hard|git push --force(?!-with-lease)|DROP TABLE'; then
  jq -n '{
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      permissionDecision: "deny",
      permissionDecisionReason: "위험한 명령어가 프로젝트 정책에 의해 차단되었습니다."
    }
  }'
  exit 0
fi

exit 0

이렇게 해 두면 적어도 "에이전트가 신나게 정리하다가 저장소를 갈아엎는" 종류의 사고는 크게 줄어든다.1

여기서 한 단계 더 나가면 파일 보호도 걸 수 있다. .env, package-lock.json, pnpm-lock.yaml, .git/, production/ 아래 파일 같은 것들이다. 다만 이 부분에서 중요한 진실이 하나 있다.

PreToolUse는 도구 호출을 가로챌 뿐, 에이전트가 이미 문맥에 넣어 버린 정보를 지우지는 못한다.3

예를 들어 .env를 읽는 것을 막지 않았다고 해 보자. 에이전트가 한 번 그 내용을 읽어 버리면, 이후에는 echo, 로그 파일 쓰기, 다른 파일에 복사, 상태 메시지 출력처럼 훨씬 다양한 우회 경로가 생긴다. 즉, 훅은 실행 정책을 다루지, 에이전트의 의미적 의도 전체를 봉인하지는 못한다.3

그래서 민감 정보 보호는 훅 하나로 끝내면 안 된다. 보통은 세 겹이 필요하다.

  • permissions 규칙으로 읽기/쓰기 자체를 제한한다.

  • PreToolUse 훅으로 위험 명령과 우회 시도를 추가로 막는다.

  • 환경 격리로 비밀과 호스트 자원을 가능한 한 작업 공간 밖에 둔다.

Claude Code는 .claude/settings.json의 permissions에서 deny, ask, allow 규칙을 둘 수 있다. 예를 들어 .env나 secrets/에 대한 읽기를 아예 금지하고, production/에 대한 쓰기는 질문하도록 만들 수 있다.4

{
  "permissions": {
    "deny": [
      "Read(./.env)",
      "Read(./secrets/**)"
    ],
    "ask": [
      "Write(./production/**)",
      "Bash(git push:*)"
    ]
  }
}

이 조합이 중요한 이유는 역할 분담이 분명하기 때문이다. permissions는 담장이고, Hooks는 경비원이다. 담장이 없는 경비원은 뛰어다니다 지친다. 경비원 없는 담장은 낮다. 둘 다 있어야 한다.

실전에서는 차단 강도를 세 단계로 나누면 운영이 편하다.

  • 절대 안 되는 것: deny

  • 가끔 필요하지만 비싼 것: ask

  • 자주 하고 안전한 것: allow 또는 자동 처리

이렇게 해야 모든 걸 막는 바람에 속도가 죽는 일을 피할 수 있다. 좋은 훅 시스템은 개발자를 괴롭히지 않는다. 진짜 비싼 실수만 귀찮게 한다.

9.4 Hook으로 워크플로우 게이트 만들기

이제 훅을 단발성 차단기가 아니라, 흐름을 만드는 게이트로 보자. 솔로 빌더에게 정말 유용한 것은 "rm -rf 방지" 하나보다, 작업 전체가 일정한 품질선을 넘기 전에는 다음 단계로 못 넘어가게 하는 얇은 레일이다.

가장 실용적인 설계는 앞 장의 서브에이전트 파이프라인과 맞물린다.

구현자는 파일을 수정한다. 그러면 PostToolUse가 포맷팅과 린트를 붙인다. 리뷰어 서브에이전트가 끝나면 SubagentStop이 리뷰 요약을 남긴다. 마지막으로 메인 세션이 Stop에 도달하면 빌드와 테스트를 확인하고, 통과한 경우에만 "이번 작업 완료"라고 말하게 만든다.15

즉, 흐름은 이렇게 된다.

구현 → 자동 정리 → 리뷰 종료 확인 → 최종 검증 → 완료 선언

이 구조가 좋은 이유는 사람이 매번 "린트 했지?", "테스트 돌렸지?", "리뷰 끝났지?"를 되묻지 않아도 되기 때문이다. 혼자 일할 때 가장 피곤한 일은 코드를 짜는 일보다, 내가 했어야 할 확인을 계속 기억하는 일이다. Hooks는 이 기억 부담을 바깥으로 꺼낸다.

솔로 빌더용 최소 구성은 세 단계면 충분하다.

  • 안전 게이트: PreToolUse로 삭제, force push, 민감 경로 쓰기 차단

  • 품질 게이트: PostToolUse로 포맷팅, lint, 가벼운 테스트 자동화

  • 마감 게이트: Stop으로 빌드, 핵심 테스트, 남은 리스크 요약

여기에 서브에이전트를 적극적으로 쓴다면 SubagentStop을 붙여 역할별 핸드오프를 더 선명하게 만들 수 있다. 예를 들어 리뷰어가 끝날 때 critical, warning, suggestion 세 구역으로 분류한 요약을 저장하고, Stop에서 critical이 남아 있으면 완료 선언을 막는 식이다. 그러면 "리뷰는 했는데 아무도 읽지 않았다"는 웃기고 슬픈 상황이 줄어든다.

다만 여기서도 조심할 점이 있다. 게이트를 많이 세운다고 품질이 자동으로 올라가지는 않는다. 지난 장의 게이트키핑 함정이 훅에서도 그대로 반복된다. 작은 변경에 거대한 파이프라인을 태우면, 자동화가 아니라 관료제가 된다.

그래서 훅은 항상 가볍게 시작해서 점진적으로 조이는 방식이 좋다.

첫 단계는 로그만 남긴다. 어떤 명령이 자주 실행되는지, 어떤 파일이 자주 깨지는지 본다. 둘째 단계는 경고를 준다. 셋째 단계에서야 정말 차단한다. 이 순서를 지키면 프로젝트에 맞는 정책이 생긴다. 처음부터 다 막아 버리면 정책이 아니라 반발심이 생긴다.

운영 기준도 단순해야 한다.

  • 자주 발생하고 복구 비용이 큰 사고는 자동 차단

  • 자주 발생하지만 자동 수정 가능한 문제는 자동 처리

  • 드물지만 판단이 필요한 문제는 질문 또는 요약

이 셋만 구분해도 훅은 훨씬 똑똑해진다. 사실 똑똑해진다기보다, 더 인간적으로 된다. 사람도 작은 실수는 조용히 고치고, 큰 실수는 크게 막고, 애매한 일은 묻는 편이 가장 일하기 좋다.

브레이크가 좋은 차가 더 빨리 달리듯, 훅이 잘 설계된 워크플로우는 더 공격적으로 개발해도 덜 불안하다. 이게 솔로 빌더에게 주는 진짜 가치다. 혼자니까 느리게 가는 게 아니라, 혼자라서 더 안전하게 빨라질 수 있는 구조를 갖추는 것.

서브에이전트가 혼자 일하는 사람에게 팀의 모양을 만들어 줬다면, Hooks는 그 팀이 자동으로 움직이는 리듬을 만든다. 구현자는 손을 빠르게 움직이고, 리뷰어는 뒤늦게 불려 나오지 않으며, 메인 세션은 마지막에만 진짜 중요한 판단을 한다.

좋은 Hooks의 목표는 에이전트를 꽁꽁 묶는 것이 아니다. 반대로, 묶지 않아도 되는 영역을 늘리는 것이다. 위험한 일만 자동으로 걸러지고, 나머지는 더 자연스럽게 흘러가게 만드는 것. 그때부터 자동화는 무서운 것이 아니라, 혼자 만드는 사람의 자신감이 된다.

다음 장에서는 이런 레일을 더 높은 수준으로 끌어올리는 도구, 즉 Skills를 다룬다. Hooks가 특정 순간 자동으로 개입하는 장치라면, Skills는 반복 가능한 작업 방식 자체를 재사용 가능한 모듈로 만드는 방법이다.

참고

1Claude Code Hooks 레퍼런스

2Claude Code Hooks 가이드

3Why Claude Code PreToolUse Hooks Can Still Be Bypassed

4Cursor, Codex, Claude Code 권한 제한 실전 가이드

5Self-Validating Agents in Claude Code

책 목록

클로드 코드 솔로 빌더: 혼자 만들고 운영하는 AI 제품 개발 시스템

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기