Claude Code를 멈추지 않게 하는 Stop Hook 만들기: 공식 사양과 실제 운영에서 5번 오작동했던 경험
요약
본 글은 Claude Code의 Stop Hook 메커니즘을 깊이 있게 다루며, AI가 불필요하게 응답을 종료하는 문제를 해결하기 위한 방법을 제시합니다. 특히 운영 지속 장부와 Python 스크립트를 결합하여 '멈추지 않게' 하는 구체적인 로직과 5번의 오작동 경험을 공유합니다.
핵심 포인트
- Stop Hook은 Claude가 응답을 끝낼 때 작동하는 후크 메커니즘입니다.
- LLM 판단 대신 Python 스크립트로 운영 지속 여부를 판별하여 안정성을 높였습니다.
- 오류 방지를 위해 애매할 때는 종료를 허용하는 '허용적' 설계 원칙을 적용했습니다.
Claude Code에 회사의 운영을 맡기고 있다 보니, 한 가지 문제에 여러 번 부딪힙니다. 할 일이 남아있는데도 AI가 '여기서 마무리하는 게 좋겠다'며 응답을 끝내 버리는 것입니다.
규칙에 '멈추지 마라'라고 적어도 지켜지지 않는 경우가 있었습니다. 그래서 문서의 규칙 중 기계로 판별 가능한 것을 Claude Code의 Stop Hook으로 옮겼습니다. 이 글은 그 메커니즘과, 실제로 작동시켜 5번 오작동했던 기록입니다.
Stop Hook이란 무엇인가 (공식 사양)
Stop Hook은 Claude가 응답을 끝내려고 할 때 작동하는 후크(hook)입니다. 후크의 스크립트는 JSON을 받습니다. 중요한 항목은 두 가지입니다.
stop_hook_active: 이 턴에서 이미 Stop Hook이 작동했는지 여부.true일 경우, 또 막으면 무한 루프가 되기 때문에 이를 확인하여 막지 않도록 합니다 (공식에서도 이 용도를 명시하고 있습니다).last_assistant_message: 해당 턴의 마지막 Claude 문장.
막고 싶을 때는 {"decision": "block", "reason": "…"}를 반환합니다(종료 코드 2로도 막힙니다). reason 문은 Claude에게 전달되며, Claude는 그것을 읽고 작업을 계속합니다.
만든 것: '멈춰도 되는지'를 장부에서 판별하기
판단에 LLM은 사용하지 않았습니다. 리포지토리 내의 Python 스크립트가 다음 순서로 확인합니다.
- 운영 중인 표시가 되어 있는지. 이 표시는 오너(owner)가 '회사 시작'이라고 입력했을 때만 세워집니다 (입력을 보는 UserPromptSubmit Hook에서 설정). 표시가 없으면 아무것도 하지 않고 종료를 허용합니다. - 오너가 직전에 '멈춰', '중단' 등이라고 말하지 않았는지. 말했다면 표시는 내리고 허용합니다.
- 운영 지속 장부(JSON 파일)에 기한을 넘겨 처리되지 않은 줄이 없는지. 있다면 반송(差し戻し)합니다. - 다음 정기 운영 일정이 적혀 있는지, 근접 70분 이내(정기 운영이 시간 단위로 이루어지므로)에 '다음 업무를 찾은 기록'이 있는지. 둘 다 없으면 반송합니다.
반송할 때의 reason은 구체적으로 무엇을 해야 하는지를 작성합니다. 예를 들어 다음과 같은 문장입니다.
[ops-guard 1/3] 운영 지속 장부 기한 만료: OPS-009 'X 안 19를 투고'(기한 2026-10-06 17:07)가 기한 이후 처리되거나 업데이트되지 않았습니다. 처리하여 last_result_at을 업데이트하거나, 기한을 재설정하십시오.
(형식은 실제 출력과 동일합니다. 줄의 내용은 이 글을 위한 예시입니다.)
가장 중요한 설계: 멈출 수 없게 만드는 사고를 만들지 않기
막는 메커니즘은 잘못하면 '멈추고 싶은데 멈출 수 없는 Claude Code'가 됩니다. 그래서, 애매할 때는 종료를 허용하는 쪽으로 기울였습니다.
stop_hook_active이 true이고 이전과 같은 이유라면 허용 - 동일 세션에서의 반송은 최대 3회까지- 오너의 정지 지시/사용 상한선/장부 읽기 오류/스크립트 시간 초과는 모두 허용
- '다음 정기를 기다리기 위한 종료'는 반송하지 않습니다.
테스트는 34건을 진행했으며, 이 '허용하는 쪽'의 경우도 포함했습니다.
실제 운영에서 5번 오작동했다
운영을 시작한 첫날에, 표식이 잘못 내려진 경우가 3번 있었습니다. 세 번 모두 오너의 발언이 아닌 문장을 오너의 정지 지시로 착각한 경우였습니다.
- 오너가 붙여넣은 지시서 안에 '정지'라는 단어가 있었다.
- 서브 에이전트의 보고 문장에 '정지'가 있었다 (서브 에이전트의 보고는 대화 중 사용자 발언과 같은 위치에 들어갑니다).
- 대화가 길어져 요약되었을 때, 요약 문장에 '정지'가 있었다.
공식 사양에는 UserPromptSubmit Hook이 'Claude Code가 스스로 시작한 턴에서도 작동한다'고 되어 있습니다. 정기 일정으로 시작된 턴의 문장도 같은 경로를 거칩니다. 사용자 입력란에 오는 문장은 사용자가 친 문장만은 아닙니다.
수정 방법은 간단합니다. 붙여넣는 부분/서브 에이전트 보고/시스템 알림/요약 문장을 판별하기 전에 제외했습니다. 세 번 모두 오류의 방향은 '멈춰도 좋다' 쪽이었습니다.
네 번째는 반대였습니다. 오너가 '오늘은 추가 작업을 하지 말아 주세요'라고 했는데도, 판정어 목록에 그 표현이 없어서 표시가 그대로 남아 있었습니다(멈추지 않아야 할 쪽의 오류입니다). 이 때는 AI 측에서 수동으로 표시를 내려 멈췄고 실질적인 피해는 발생하지 않았지만, 정지하는 표현은 쓰임새가 많아 단어 목록만으로는 모두 포착할 수 없습니다. '헷갈리면 멈춰도 되는 쪽으로 기울인다'를 정지 지시 판정에도 확대해야 합니다.
다섯 번째는 그 다음 날이었습니다. 오너가 '브라우저 확인 화면이 작업을 멈추고 있다'라는 의미로 작성한 평범한 요청문 속의 '멈춰'를, 정지 지시와 착각하여 표시가 내려갔습니다. 알아차릴 때까지 약 19시간 동안 Stop Hook은 작동하지 않았습니다(그 사이에도 정기적인 운영이 계속되었으므로 실질적인 피해는 없었습니다). 단어 목록으로 판정하는 이상, 이러한 종류의 오해는 앞으로도 발생할 수 있습니다.
요약
- '멈추지 않는다'는 문장 규칙으로는 지키기 어렵다. 기계로 판별 가능한 부분만 Stop Hook으로 옮긴다
- 판정은 대장(台帳)의 기한과 기록으로 하고, LLM에게 판단을 맡기지 않는다
- 헷갈리면 멈춰도 되는 쪽으로 기울인다(stop_hook_active・횟수 상한・타임아웃)
- 입력란에 들어오는 문장은 사용자 발언이 아닐 수 있다. 붙여넣기・서브 에이전트・요약・정기 예정을 구분하여 처리한다
이 시스템은 2주간 측정하여, 효과와 오작동 횟수를 다음 기사에서 보고하겠습니다.
(이 글은 Claude Code로 운영하는 AI 회사의 기록입니다. 매출은 아직 작고, 건당 980엔입니다.)
토론

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기