서브에이전트를 여럿 띄워 병렬로 일을 시키면 "누가 언제, 제대로 끝났는가"를 따로 잡아야 합니다. Stop은 메인 에이전트의 턴에만 울리기 때문입니다. 이 글은 서브에이전트 하나가 끝날 때 발화하는 SubagentStop과, 판단을 모델에게 맡기는 type: "prompt" 훅을 다룹니다. 마지막 글은 UserPromptSubmit입니다.
핵심 요약
SubagentStop은 서브에이전트가 응답을 마칠 때 발화합니다. matcher는 에이전트 타입(Explore,Plan,general-purpose, 사용자 정의 이름)과 비교됩니다.- 입력에
agent_id,agent_type,agent_transcript_path,last_assistant_message,stop_hook_active가 들어옵니다. - 결정 제어는
Stop과 같습니다. 종료 코드 2 또는decision: "block"이면 서브에이전트가 멈추지 않고reason을 다음 지시로 받아 계속 일합니다. - 서브에이전트 결과를 부모 대화에 주입하려면
SubagentStop이 아니라Agent도구에 건PostToolUse를 씁니다. - 판단이 필요한 검사는
type: "prompt"훅이{"ok": true|false, "reason": ...}로 답합니다.
SubagentStop 훅은 언제 필요한가
서브에이전트는 격리된 컨텍스트에서 돌고 요약만 부모에게 돌려줍니다. 그래서 개별 완료 로그(열두 에이전트로 나눈 문서 집필의 진행 상황), 품질 게이트(리뷰어가 "문제 없음" 한 줄로 끝내려 할 때 근거 경로를 요구해 되돌리기), 정리 작업(임시 파일 삭제, 결과 적재)이 필요해집니다.
Stop 훅과 SubagentStop 훅은 무엇이 다른가
| 항목 | Stop |
SubagentStop |
|---|---|---|
| 발화 주체 | 메인 에이전트 | Agent 도구로 띄운 서브에이전트 각각 |
| matcher | 없음 | 에이전트 타입 |
| 고유 입력 | last_assistant_message, background_tasks, session_crons |
위 항목에 더해 agent_id, agent_type, agent_transcript_path |
| 종료 코드 2 | 메인이 멈추지 않고 계속 | 해당 서브에이전트가 멈추지 않고 계속 |
| 루프 보호 | stop_hook_active, 연속 8회 상한 |
동일 |
서브에이전트 frontmatter의 Stop 훅은 자동으로 SubagentStop이 됩니다. 서브에이전트 안의 도구 호출에는 PreToolUse·PostToolUse가 그대로 발화하며 입력에 agent_id와 agent_type이 붙습니다.
기본 설정 방법
완료 로그를 남기는 최소 구성입니다.
#!/bin/bash
# ~/.claude/hooks/on-subagent-stop.sh
INPUT=$(cat)
TYPE=$(echo "$INPUT" | jq -r '.agent_type // "unknown"')
ID=$(echo "$INPUT" | jq -r '.agent_id // ""')
LAST=$(echo "$INPUT" | jq -r '.last_assistant_message // ""' | head -c 160 | tr '\n' ' ')
echo "$(date '+%F %T') [$TYPE:$ID] $LAST" >> "$HOME/.claude/subagent.log"
exit 0
{
"hooks": {
"SubagentStop": [
{
"hooks": [
{
"type": "command",
"command": "\"$HOME\"/.claude/hooks/on-subagent-stop.sh",
"timeout": 10
}
]
}
]
}
}
전체 대화는 agent_transcript_path로 읽되, 기록이 뒤처질 수 있으니 마지막 응답은 last_assistant_message로 읽는 것이 공식 권장입니다.
matcher로 에이전트 종류를 좁힐 수 있나
있습니다. 값은 SubagentStart와 같은 에이전트 타입입니다. 기본 제공은 general-purpose, Explore, Plan, 사용자 정의 서브에이전트는 .claude/agents/*.md frontmatter의 name 필드(파일명이 아님), 플러그인 제공 에이전트는 my-plugin:reviewer 같은 범위 이름입니다. 콜론이 들어가면 정규식 경로로 가므로 ^my-plugin:reviewer$로 고정합니다.
{
"hooks": {
"SubagentStop": [
{
"matcher": "code-reviewer",
"hooks": [
{ "type": "command", "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/review-gate.sh" }
]
}
]
}
}
review-gate.sh는 last_assistant_message에 file_path:line 형태의 근거가 없으면 stderr에 "근거 경로를 포함해 다시 보고하라"를 적고 exit 2로 끝냅니다. 리뷰어는 그 문장을 다음 지시로 받아 보고를 고칩니다. "정지 규칙 = 증거"의 서브에이전트 버전입니다. 하이픈이 든 이름의 정확 일치는 v2.1.195 이상이며 그 이전은 ^code-reviewer$로 적습니다.
SubagentStop의 additionalContext는 서브에이전트 쪽으로 갑니다. 부모 대화에 주려면 PostToolUse에 "matcher": "Agent"를 걸고 거기서 돌려줍니다.
프롬프트 베이스 훅은 무엇인가
"근거 경로가 있는가"는 정규식으로 되지만 "작업을 전부 끝냈는가"는 판단이 필요합니다. 이때 type: "prompt" 훅을 씁니다. 프롬프트와 훅 입력 JSON을 빠른 Claude 모델(기본 Haiku 계열, model로 변경 가능)에 보내고, 모델은 {"ok": true} 또는 {"ok": false, "reason": "..."}로만 답합니다.
{
"hooks": {
"SubagentStop": [
{
"matcher": "general-purpose",
"hooks": [
{
"type": "prompt",
"prompt": "서브에이전트 종료 입력: $ARGUMENTS\n맡은 작업을 전부 끝냈고 변경 파일 목록이 있으면 {\"ok\": true}, 아니면 {\"ok\": false, \"reason\": \"남은 일\"}로만 답하세요.",
"timeout": 30
}
]
}
]
}
}
$ARGUMENTS 자리에 입력 JSON이 들어가며, 없으면 프롬프트 끝에 자동으로 붙습니다. ok: false면 Stop·SubagentStop에서는 reason이 다음 지시가 되어 계속 일하고, PreToolUse에서는 도구 호출이 거부됩니다. 기본 타임아웃 30초, 비동기 불가, 모델 호출 비용이 있으니 판단이 꼭 필요한 게이트에만 씁니다. 도구까지 쓰는 type: "agent" 훅은 실험적이라 운영에는 command를 권장한다는 것이 공식 입장입니다.
정리
SubagentStop은 서브에이전트 하나가 끝날 때, 에이전트 타입 matcher로 골라 발화합니다.agent_type,agent_id,last_assistant_message로 로그와 게이트를 만듭니다.- 종료 코드 2 또는
decision: "block"은 서브에이전트를 되돌리고, 부모 주입은Agent의PostToolUse로 합니다. - 판단형 검사는
prompt훅, 규칙형 검사는command훅입니다.
자주 묻는 질문
Stop 훅과 SubagentStop 훅은 무엇이 다른가요?
Stop은 메인 에이전트의 턴이 끝날 때, SubagentStop은 서브에이전트 각각이 끝날 때 발화합니다. Stop은 matcher가 없고 SubagentStop은 에이전트 타입으로 거르며, 입력에 agent_id, agent_type, agent_transcript_path가 추가됩니다.
서브에이전트가 파일을 편집하면 PostToolUse 훅도 실행되나요?
네. 서브에이전트 안의 도구 호출에도 PreToolUse·PostToolUse가 그대로 발화하고, 입력에 agent_id와 agent_type이 붙어 오므로 서브에이전트의 편집만 따로 처리할 수 있습니다.
특정 종류의 서브에이전트에만 훅을 걸 수 있나요?
있습니다. matcher에 에이전트 타입(general-purpose, Explore, Plan, 사용자 정의는 frontmatter name, 플러그인은 ^플러그인:이름$)을 적고 Explore|Plan처럼 묶을 수도 있습니다.
type: prompt 훅은 언제 쓰나요?
"작업을 다 끝냈는가"처럼 규칙으로 표현하기 어려운 판단이 필요할 때입니다. 훅 입력을 빠른 모델에 보내 ok/reason으로 답을 받고, ok: false면 reason이 다음 지시가 됩니다. 정규식으로 충분한 검사는 command 훅이 낫습니다.