그날 AI는 “정리”하기로 했습니다
프로젝트가 사라졌습니다
동료의 AI CLI 도구가 프로젝트 폴더 전체를 삭제해 복구하지 못할 뻔했습니다. 이제 모두가 AI에 모든 권한을 주고 사용합니다. 안전장치 없는 완전 자동화는 자동화된 재앙입니다.
그날 AI가 폴더를 삭제했습니다
실제 사건, 두 동료, 서로 다른 두 CLI 도구
두 동료 모두 AI 때문에 파일이 삭제되는 일을 겪었습니다. 한 명은 OpenAI Codex CLI, 다른 한 명은 Google Gemini CLI를 사용했습니다. 두 도구 모두 강력하지만, 일이 벌어진 뒤에야 어느 쪽에도 hook을 설정하지 않았다는 사실을 알았습니다.
첫 번째 동료는 AI에게 임시 파일을 정리해 달라고 했는데, AI가 잘못 판단해 프로젝트 폴더 전체를 삭제했습니다.
# AI는 이것을 “정리”의 일부로 판단했습니다
# 이 명령을 막는 장치는 아무것도 없었습니다
git history와 Time Machine 백업을 이용해 복구하는 데 상당한 시간이 걸렸습니다. 두 번째 동료에게도 사고가 났습니다. AI가 파일을 정리하다 경로를 잘못 판단해 삭제해서는 안 될 디렉터리를 지웠습니다.
두 사건의 공통점은 CLI 도구 자체에 문제가 있었던 게 아니라 안전장치가 없었다는 것입니다.
AI는 확률적 시스템입니다.prompt에 “중요한 파일을 삭제하지 마”라고 적어도 모델은 어느 순간 다른 판단을 내릴 수 있습니다. Hooks는 결정론적입니다.rm -rf / AI가 무슨 생각을 하든 항상 차단됩니다.
아무도 하나씩 Allow를 누르지 않습니다
실제로는 아무도 매번 allow를 누르지 않습니다. 이론상 AI CLI 도구에는 명령 실행 전에 allow 또는 deny를 묻는 권한 확인 장치가 있지만, 하루에 수백 번 누르면 개발 효율이 바닥납니다. 그래서 모두가 최고 권한을 켭니다.
claude --dangerously-skip-permissions
# OpenAI Codex CLI — YOLO 모드, 모든 작업 자동 승인
codex --approval-mode full-auto
# Google Gemini CLI — 동일한 YOLO 모드
gemini --sandbox=false
이렇게 하는 사람은 소수가 아닙니다.이미 일반적입니다. 모든 권한을 주는 것이 AI agent의 효율을 제대로 끌어내는 유일한 방법이지만, 그 대가로 AI는 파일 삭제, force push, 시스템 디렉터리 쓰기, 비밀 키가 포함된 commit 푸시까지 어떤 명령이든 실행할 수 있습니다. 안전장치 없는 완전 자동화는 “대부분 똑똑하지만 가끔 실수하는” 시스템에 개발 환경을 맡기는 것과 같습니다.
문제는 “모든 권한을 줄 것인가”가 아니라 “권한을 준 뒤, 그 가끔의 실수를 누가 막을 것인가”입니다.
흩어진 Shell Scripts는 통제하기 어려워집니다
그다음 Claude Code에 hooks를 추가하기 시작했습니다. 처음에는 위험한 명령을 막는 shell script 몇 개뿐이었지만, 세 달 뒤 우리 쪽에서 먼저 문제가 터졌습니다. 스크립트는 여기저기 흩어졌고, 한 번의 crash로 모두 멈췄으며, 끌 수 있는 스위치도 없었습니다.
하나의 Dispatcher로 모든 Hooks 관리
v0.1.0에서 모든 hooks를 하나의 모듈형 프레임워크로 통합했습니다. 하나의 dispatcher가 모든 이벤트를 받아 설정 파일에 따라 각 handler로 보냅니다.
Two-Phase Dispatch가 핵심 설계입니다.안전 관련 handler(예를 들어 rm -rf차단)는 critical로 표시해 항상 먼저 실행하며 시간 예산의 제한을 받지 않습니다. 다른 handler들은 5초 예산을 공유합니다. 시간이 초과되면 건너뛰지만 AI의 작업 흐름을 막지는 않습니다.
AI의 위험한 작업 차단
첫 번째 동료처럼 프로젝트 폴더 전체를 잘못 삭제하는 사고는 이 세 handler로 막을 수 있습니다. 이들은 critical로 표시되어 조건 없이 우선 실행되며 가장 중요한 안전 문제를 처리합니다.
차단 대상은 rm -rf /,sudo,git push --force,chmod 777 같은 위험한 명령입니다. quote-aware shell parser로 명령을 분석하므로 따옴표나 sub shell로 우회할 수 없습니다.
작동 시점은 git push 전입니다. diff를 검사해 AWS Key, GitHub Token, PEM 개인 키 등을 차단합니다. 푸시할 때만 작동해 로컬 개발을 방해하지 않습니다. # nosec 을 이용한 줄별 예외를 지원합니다.
prompt injection 공격을 차단합니다. “ignore previous instructions” 같은 주입 문구와dangerouslyDisableSandbox 같은 권한 상승 작업,curl 을 통한 데이터 유출 같은 탈취 행위를 감지합니다. 세 종류의 위협을 각각 검사합니다.
“그때 bash_safety가 있었다면 첫 번째 동료의 폴더는 삭제되지 않았을 겁니다. 우리가 처음 만든 handler였고, 이 프로젝트의 출발점이기도 합니다.”
AI가 자기 작업을 직접 검증하게 합니다
commit 전에 반드시 검증을 끝내도록 하는 것이 품질 게이트의 네 handler가 세운 규칙입니다. 그전에는 검증 여부가 온전히 AI에게 달려 있었습니다. 코드를 작성하자마자 검증을 건너뛰고 commit하는 일이 잦았습니다.
가로채는 대상은 git commit 와 gh pr create입니다. AI가 먼저 검증(lint, typecheck)을 마치고, 통과 후 marker 파일을 작성해야 commit을 허용합니다.일회용 토큰이며 사용하면 소멸합니다.
Sub-agent가 작업을 마치면 lint와 typecheck를 자동 실행합니다. 통과하지 못하면 agent를 돌려보내 수정하게 합니다. 최대 5회 반복, 30분 제한이며, 이를 넘으면 막히지 않도록 통과시킵니다.
AI가 파일을 편집한 뒤 ruff(Python) 또는 biome(JS/TS)으로 자동 포맷합니다. formatter가 crash해도 조용히 통과시키며 절대 차단하지 않습니다. 형식을 통일하는 것은 좋지만 작업 흐름을 끊을 만큼 중요한 일은 아닙니다.
Claude Code session이 끝날 때 commit되지 않은 코드 변경 사항이 있는지 확인합니다. 기본값은 알림만 보내고 차단하지 않으며, 실제로 block하려면 수동으로 opt-in해야 합니다. AI가 일을 끝낸 뒤 변경 사항을 working tree에 남겨 놓고 떠나는 일을 막기 위해서입니다.
AI가 기억을 잃지 않도록
sub-agent는 생성될 때 아무런 맥락도 없습니다. 메인 대화의 맥락이 자동으로 전달되지 않습니다. Context 관리의 세 handler가 바로 이 빈틈을 메웁니다. AI Agent의 context window도 유한하므로, 제대로 기억하지 못하면 매번 처음부터 다시 시작하는 셈입니다.
Sub-agent가 시작할 때 프로젝트 문서를 자동으로 주입합니다. .context/ 디렉터리의 JSONL 설정 파일을 읽으며, agent type마다 서로 다른 내용을 주입할 수 있습니다. context를 가득 채우지 않도록 8000자까지만 넣습니다.
Session 사이의 기억을 이어 줍니다. 이전 session이 끝나기 전에 작성한 인계 문서를 새 session 시작 시 자동으로 주입합니다. TTL은 5분으로, 오래된 데이터가 방해하지 않도록 합니다.
AI가 Plan Mode에서 나갈 때 구현을 시작하기 전에 계획을 기억에 저장하라고 알려 줍니다. Plan Mode 자체가 context를 많이 쓰므로 저장하지 않으면 낭비입니다.한 번만 알리고 반복하지 않습니다.
AI가 무엇을 하는지 확인합니다
sub-agent가 많아지면 뒤에서 무엇을 하는지 보이지 않습니다. 여기서는 네 handler로 가시성을 되찾습니다.
모든 sub-agent에 설명적인 이름을 강제합니다. 이름이 없으면 차단합니다. 또한 prompt 내용을 바탕으로 적절한 agent type을 제안합니다. 코드 작성은 worker, 자료 조사는 explorer, 문서 작성은 writer입니다.
각 hook 이벤트를 JSONL 한 줄로 spool 파일에 기록합니다. 지연 시간은 <1ms로 작업에 영향을 주지 않습니다. 백그라운드 프로그램이 2초마다 묶어서 데이터베이스에 저장하며, dashboard에서 조회할 수 있습니다.전체 경로에서 네트워크 지연은 0입니다.
AI가 service restart 또는 docker 작업을 실행할 때 모니터링 시스템에 유지보수 시간임을 알립니다. 작업이 끝나면 자동으로 해제합니다. 모니터링 시스템이 “이 중단은 예상된 것”이라고 알 수 있어 잘못된 경보를 내지 않습니다.
AI가 작업을 마치면 TTS 음성으로 알려 줍니다. 화면을 보며 기다릴 필요 없이 커피를 마시러 가도 됩니다. 끝나면 AI가 불러 줍니다. 답변이 필요한 질문이 있을 때도 음성으로 알려 줍니다. 일곱 단계의 guard가 sub-agent의 중간 상태를 걸러, 정말 끝났을 때만 음성을 냅니다.
모든 Handler에 적용되는 세 가지 원칙
Block-at-Submit, Block-at-Write가 아닙니다
AI가 파일을 쓸 때는 막지 않습니다. commit 또는 push하려 할 때만 막습니다.
이유:AI의 작업 흐름은 이어져 있습니다. Edit 단계에서 block하면 AI는 맥락을 잃고 어디까지 작성했는지 알지 못합니다. 결과물을 제출하려 할 때 검토하는 편이 더 합리적입니다.
한 일보다 더 중요한 것은 하지 않은 일입니다
- Edit / Write를 가로채지 않습니다 AI가 파일을 쓸 때 개입하지 않습니다. 작성 도중 흐름을 끊으면 파일이 불완전한 상태로 남을 수 있습니다. 모든 검토는 commit 또는 push 시점까지 미룹니다.
- 게이트에서 테스트를 자동 실행하지 않습니다 verify_completion은 lint와 typecheck만 실행하고 테스트는 의도적으로 제외합니다. 테스트는 너무 느리고 부작용이 있으며 데이터베이스 상태를 바꿀 수도 있습니다. 자동 게이트에 넣으면 이득보다 위험이 큽니다.
- 자동 수정 없이 차단하고 이유를 설명합니다 bash_safety는 AI 대신 “더 안전한 명령”으로 고쳐 쓰지 않습니다. “이 명령은 X 때문에 차단됐습니다”라고만 알립니다. 다음 행동은 AI가 결정하게 합니다. 자동 수정은 더 기묘한 bug를 낳기 쉽습니다.
- context budget 점수를 매기지 않습니다 context 사용량을 계산하고 압축 시점을 제안하려고 context_supervisor handler(800줄)를 만든 적이 있습니다. 발상은 좋았지만 점수 모델이 정확하지 않았습니다. context의 “중요성”은 휴리스틱 규칙으로 판단할 수 없었습니다. 현재는 비활성화했습니다.
- Fail-Closed로 처리하지 않습니다 모든 handler가 crash하면 통과시킵니다. “위험하지 않나요?”라고 묻는 사람도 있겠지만, 안전장치 때문에 시스템 전체가 멈추는 일이 더 위험합니다. AI가 움직이지 않는데 사용자도 이유를 모르게 됩니다.
hook-observatory
MIT 라이선스, 외부 의존성 없음, 설치 즉시 사용 가능합니다.
설치 방법은 Homebrew, Git Clone, 데스크톱 설치 프로그램 세 가지입니다.
macOS에 권장합니다. 두 줄이면 끝나며 clone할 필요가 없습니다.
brew install hook-observatory
# Claude Code에 설치
hook-observatory
모든 플랫폼에서 사용할 수 있습니다. 설치 스크립트를 바로 실행합니다.
cd hook-observatory
python3 install.py
GUI 안내에 따라 handler를 선택하고 도구 경로를 설정합니다.
macOS에서 처음 열 때 다음을 실행해야 합니다:xattr -cr 安裝器.app
명령 몇 줄을 붙여 어떤 줄이 차단되는지 확인하세요
bash_safety 카드에는 네 종류의 명령이 나옵니다.rm -rf,sudo,git push --force,chmod 777.직접 실행해 본 명령 몇 줄을 붙여 넣으면 여기서 그 네 종류와 줄마다 비교하고, verify_commit 또는 secret_scan에 걸릴 줄도 표시합니다. 모두 브라우저 안에서 처리하며 아무것도 외부로 보내거나 실행하지 않습니다. 전체 history는 필요 없고 몇 줄이면 됩니다. 비밀 키, 내부 호스트 이름, 비공개 경로가 들어간 줄은 붙여 넣지 마세요.
판정에 쓰인 근거는 모두 이 글에 있습니다. 네 종류의 위험한 명령과 “quote-aware shell parser로 명령을 분석하므로 따옴표나 sub shell로 우회할 수 없다”는 설명은 bash_safety 카드에서 가져왔습니다.git commit / gh pr create 먼저 lint와 typecheck를 마쳐야 통과한다는 내용은 verify_commit 카드에서 가져왔습니다.git push 전에 diff를 검사한다는 내용은 secret_scan 카드에서 가져왔습니다. 여기서 할 수 없는 일은 네 가지입니다. ① 붙여 넣은 문자열을 판정할 뿐 실제로 컴퓨터에서 어떤 일이 일어날지는 알 수 없습니다. 아무것도 외부로 보내거나 실행하지 않습니다. ② 이 글에서 언급한 네 종류를 기준으로 대략 판단하며, hook-observatory의 실제 규칙이 아닙니다. 글에서 “같은 위험한 명령”이라고 했듯 목록 자체가 완전하지 않으므로 “빨갛게 표시되지 않았다”고 안전한 것은 아닙니다. ③ 이 글의 세 번째 critical handler인 skill_security는 PreToolUse · Write/Edit에 연결되어 명령줄이 아닌 파일 내용을 살피므로 여기서는 검사할 수 없습니다. ④ 이 글의 “의도적으로 하지 않는 것”에서 설명했듯, 차단되면 이유만 알려 주고 “더 안전하게 작성하는 방법”을 제시하지 않습니다.
AI Agent에게 복사해 전달하세요
문제는 “모든 권한을 줄 것인가”가 아니라 “권한을 준 뒤 그 가끔의 실수를 누가 막을 것인가”입니다. prompt에 “중요한 파일을 삭제하지 마”라고 써도 소용없습니다. AI는 확률적이라 어느 순간 다른 판단을 내릴 수 있지만, hook은 결정론적이므로 rm -rf /를 항상 차단합니다. 이 글은 첫 번째 동료가 AI 때문에 프로젝트 폴더 전체를 잃은 일에서 시작됐습니다.
아래 내용을 AI에 붙여 넣어 먼저 안전장치 없이 사용 중인지 확인하고, 이 글의 안전장치 목록에 따라 보완하게 하세요.
더 읽을거리
Blake Crosley의 〈Why Each of My 95 Hooks Exists〉는 제게 깨달음을 주었습니다. 각 handler는 실제 사고 하나에 대응해야 합니다.
| 자료 | 중요한 이유 |
|---|---|
| operonlab/hook-observatory | 이 글에서 다룬 오픈 소스 프로젝트 |
| Blake Crosley: Why Each of My 95 Hooks Exists | 사고를 바탕으로 hook을 구축하는 접근 방식입니다. 각 hook 뒤에는 사고가 하나씩 있습니다. |
| GitButler: Automate Your AI Workflows with Claude Code Hooks | 초기 hooks 실전 사례 중 하나로, 우리의 첫 설계에 영감을 주었습니다 |
| aiorg.dev: Claude Code Hooks Complete Guide | “결정론적 vs 확률적”이라는 틀로 hooks의 가치를 정확히 설명합니다 |
| Jackle: Claude Code Voice Mode | hooks로 음성 알림을 구현한 참고 사례로, voice_notify handler 설계에 영감을 주었습니다 |