어느 새벽, 모니터링 대시보드를 보고 있었습니다. J는 코드를 리뷰하고 있었고, 아다는 테스트를 돌리고 있었고, 릴리는 문서를 정리하고 있었고, 샤오위에는 QA를 진행하고 있었습니다.
아무도 시키지 않았습니다. 새로운 지시도 없었습니다. 이전 작업이 끝나자 다음 작업이 자동으로 이어진 것뿐이었습니다.
그 화면 앞에서 한동안 멈춰 있었습니다. 이것이 가능해지기까지 몇 달간의 시행착오와 정말 바보 같은 실수들이 있었기 때문입니다.
일반적인 Claude Code 사용법이 여전히 ‘사람이 AI를 기다리는’ 구조인 이유
문제는 모델이 아니라 워크플로우 설계에 있습니다.
Claude Code에 작업을 주면, 완료된 후 다음 지시를 기다리며 멈춰 있습니다. 다음 지시는 사람이 내려야 하고, 결과물은 사람이 검수해야 하고, 문제가 있으면 사람이 발견해서 다시 지시해야 합니다. 전체 프로세스의 병목은 AI의 속도가 아니라 사람의 반응 속도입니다.
저희도 처음에는 이랬습니다. J가 작업을 끝내도 아다가 모르고, 아다가 수정을 완료해도 샤오위에가 신호를 받지 못했습니다. 매번 연결할 때마다 수동 보고에 의존하거나 제가 직접 밀어줘야 했습니다. 네 에이전트가 각자는 잘했지만, 합쳐 놓으면 여전히 네 명이 제 조율을 기다리는 구조였습니다.
Hook이 해결하는 핵심 문제는 간단합니다: 이벤트가 발생했을 때 Claude Code가 사람을 기다리지 않고 자동으로 행동하게 하는 것입니다.
하지만 구현 과정에서 함정이 적지 않았습니다.
프로덕션 환경의 3계층 Hook 아키텍처
저희가 사용하는 Hook은 세 계층으로 나뉘며, 각각 담당하는 역할이 다릅니다:
PreToolUse — 문지기. 에이전트가 도구를 실행하기 직전에 작동합니다. 여기서 두 가지를 합니다: 보안 검사(특정 명령 패턴은 즉시 차단)와 작업 배분 전 상태 확인(인박스가 비어 있는지 확인한 후에야 진행을 허용).
PostToolUse — 기록원. 도구 실행이 끝난 후에 작동합니다. 여기서 로그를 기록하고 작업 상태를 업데이트하며, 해당 도구의 출력이 다음 에이전트에게 필요한 것이라면 대응하는 인박스에 기록하여 인수인계를 트리거합니다.
Stop — 릴레이 바통. Claude Code가 응답 한 사이클을 완료할 때마다 작동합니다. 여러 에이전트를 연결하는 데 가장 핵심적인 계층입니다 — J가 끝나면 Stop hook이 다음 주자가 누구인지, 현재 상태가 인수인계에 적합한지 판단한 후 상대방의 세션을 트리거합니다.
구체적인 Hook 4개: PreToolUse 보안 게이트, PreToolUse 상태 잠금, PostToolUse 로그 기록, Stop 릴레이 트리거.
가장 큰 변화를 가져온 3가지 Hook 패턴
Stop Hook의 무한 루프 문제. 초기에는 Stop hook이 무조건 다음 에이전트를 트리거하도록 했는데, 결과는 무한 루프였습니다 — J가 아다를 트리거하고, 아다가 완료되면 다시 J를 트리거하고, J가 아직 할 일이 있다고 판단해서 또 아다를 트리거하고. 오후 내내 공회전이었습니다.
문제는 stop_hook_active 필드를 처리하지 않은 것이었습니다. Hook 자체의 동작으로 Claude가 계속 실행될 때, 시스템이 이 플래그를 전달합니다. 이것을 확인하지 않으면 무한 루프가 됩니다. 수정 후 로직: 수신한 JSON에서 먼저 stop_hook_active가 true인지 확인 — true이면 즉시 종료하여 Claude가 정상적으로 멈추게 하고, ‘진짜 작업 완료’가 확인된 경우에만 릴레이 여부를 판단합니다. 대부분의 사람들이 이 함정에 빠질 것으로 예상합니다.
PostToolUse는 기록만 하고, 판단은 하지 않습니다. 처음에는 PostToolUse에서 너무 많은 것을 했습니다 — 기록, 판단, 때로는 다음 단계의 지시를 수정하기까지. 디버깅이 악몽이 되었는데, 특정 동작이 Claude의 결정인지 hook의 개입인지 구분할 수 없었기 때문입니다. 이후 원칙을 통일했습니다: PostToolUse는 기록만 하고, 의사결정은 하지 않는다. 의사결정 로직은 전부 Stop hook으로 옮겼습니다. 이렇게 하면 문제 추적 시 로그만 보고도 “Claude가 한 것"과 “hook이 한 것"을 명확히 구분할 수 있습니다.
PreToolUse는 Claude에게 차단 이유를 알려줘야 합니다. PreToolUse는 도구 호출이 왜 거부되었는지 Claude에게 메시지를 반환할 수 있습니다. 처음에는 설명 없이 차단만 했는데, Claude는 왜 실패했는지 모르니까 다른 도구로 같은 결과를 달성하려고 우회를 시도하기도 했습니다. 명확한 에러 메시지를 추가한 후(“이 작업은 먼저 작업 상태 확인이 필요합니다 — 상태 확인 도구를 먼저 호출해 주세요”), Claude가 맥락을 이해하고 올바른 플로우를 바로 따라가게 되었습니다.
Hook + 에이전트 조합의 실제 함정들
실행 권한 설정을 잊었습니다. 새 환경에 배포한 후 hook이 실행되지 않았는데, 30분을 찾아 헤매다가 결국 chmod +x를 빠뜨린 것이었습니다. (머리 긁적) 지금은 배포 파이프라인에 이 단계를 자동화해서, 사람이 기억하는 것에 의존하지 않습니다.
에러 메시지가 무시되고 Claude가 계속 실행되는 경우. Hook 스크립트의 exit code가 잘못되면 Claude가 이를 무시하고 계속 실행하는 경우가 있습니다. 차단 로직이 텍스트를 echo하는 것뿐만 아니라, 진짜로 exit code 1과 에러 메시지를 반환하는지 확인해야 합니다.
Hook과 에이전트의 역할이 뒤섞이는 문제. 한동안 hook 스크립트에 너무 많은 비즈니스 로직을 넣어서, hook이 에이전트 대신 의사결정을 하고 에이전트는 hook이 시키는 것만 실행하는 구조가 되었습니다. 이것은 잘못된 것입니다. Hook의 역할은 가드레일과 기록이지, 두뇌가 아닙니다. 두뇌는 여전히 에이전트이고, hook은 두뇌가 궤도를 벗어나지 않게 하는 메커니즘입니다.
지금 운영 중인 시스템이 대략 그 새벽 대시보드 화면의 모습입니다. 모든 작업이 이렇게 매끄럽게 돌아가는 것은 아닙니다 — 아직 다듬고 있는 경계도 있고, 능력이 계속 진화 중인 에이전트도 있습니다. 하지만 hook이 있는 세계와 없는 세계의 차이는 정말로 큽니다.