3-2. Hook 입력과 출력
공통 입력 필드·종료 코드·JSON 출력·결정 제어 등 hook I/O 레퍼런스
Hook 입력과 출력
Command hook은 stdin을 통해 JSON 데이터를 수신하고, 종료 코드, stdout, stderr를 통해 결과를 전달합니다. HTTP hook은 동일한 JSON을 POST 요청 본문으로 수신하고, HTTP 응답 본문을 통해 결과를 전달합니다. 이 섹션에서는 모든 이벤트에 공통적인 필드와 동작을 다룹니다. 각 이벤트의 Hook 이벤트 하위 섹션에는 해당 이벤트의 구체적인 입력 스키마와 결정 제어 옵션이 포함되어 있습니다.
공통 입력 필드
모든 hook 이벤트는 각 hook 이벤트 섹션에 문서화된 이벤트별 필드 외에 다음 필드를 JSON으로 수신합니다. Command hook의 경우 이 JSON은 stdin을 통해 전달됩니다. HTTP hook의 경우 POST 요청 본문으로 전달됩니다.
| Field | Description |
|---|---|
session_id | 현재 세션 식별자 |
transcript_path | 대화 JSON 파일 경로 |
cwd | hook이 호출될 때의 현재 작업 디렉토리 |
permission_mode | 현재 권한 모드: "default", "plan", "acceptEdits", "dontAsk", 또는 "bypassPermissions" |
hook_event_name | 발생한 이벤트의 이름 |
--agent 옵션으로 실행하거나 subagent 내부에서 실행할 때는 두 개의 추가 필드가 포함됩니다:
| Field | Description |
|---|---|
agent_id | subagent의 고유 식별자. hook이 subagent 호출 내부에서 실행될 때만 존재합니다. 이 값을 사용하여 subagent hook 호출과 메인 스레드 호출을 구분할 수 있습니다. |
agent_type | 에이전트 이름 (예: "Explore" 또는 "security-reviewer"). 세션이 --agent를 사용하거나 hook이 subagent 내부에서 실행될 때 존재합니다. subagent의 경우, subagent의 타입이 세션의 --agent 값보다 우선합니다. |
예를 들어, Bash 명령에 대한 PreToolUse hook은 stdin으로 다음과 같은 데이터를 수신합니다:
{
"session_id": "abc123",
"transcript_path": "/home/user/.claude/projects/.../transcript.jsonl",
"cwd": "/home/user/my-project",
"permission_mode": "default",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "npm test"
}
}
tool_name과 tool_input 필드는 이벤트별 필드입니다. 각 hook 이벤트 섹션에서 해당 이벤트의 추가 필드를 문서화합니다.
종료 코드 출력
hook 명령의 종료 코드는 Claude Code에 해당 작업을 진행할지, 차단할지, 무시할지를 알려줍니다.
Exit 0은 성공을 의미합니다. Claude Code는 stdout에서 JSON 출력 필드를 파싱합니다. JSON 출력은 exit 0일 때만 처리됩니다. 대부분의 이벤트에서 stdout은 verbose 모드(Ctrl+O)에서만 표시됩니다. 예외는 UserPromptSubmit과 SessionStart로, 이 경우 stdout이 Claude가 보고 활용할 수 있는 컨텍스트로 추가됩니다.
Exit 2는 차단 오류를 의미합니다. Claude Code는 stdout과 그 안의 JSON을 무시합니다. 대신 stderr 텍스트가 Claude에게 오류 메시지로 전달됩니다. 효과는 이벤트에 따라 다릅니다: PreToolUse는 도구 호출을 차단하고, UserPromptSubmit은 프롬프트를 거부하는 식입니다. 전체 목록은 exit code 2 동작을 참조하세요.
기타 모든 종료 코드는 비차단 오류입니다. stderr은 verbose 모드(Ctrl+O)에서 표시되고 실행은 계속됩니다.
예를 들어, 위험한 Bash 명령을 차단하는 hook 명령 스크립트:
#!/bin/bash
# Reads JSON input from stdin, checks the command
command=$(jq -r '.tool_input.command' < /dev/stdin)
if [[ "$command" == rm* ]]; then
echo "Blocked: rm commands are not allowed" >&2
exit 2 # Blocking error: tool call is prevented
fi
exit 0 # Success: tool call proceeds
이벤트별 Exit code 2 동작
Exit code 2는 hook이 "중지, 이 작업을 수행하지 마세요"라고 알리는 방법입니다. 효과는 이벤트에 따라 다릅니다. 일부 이벤트는 차단할 수 있는 작업을 나타내고(아직 수행되지 않은 도구 호출 등), 다른 이벤트는 이미 발생했거나 차단할 수 없는 작업을 나타내기 때문입니다.
| Hook event | Can block? | What happens on exit 2 |
|---|---|---|
PreToolUse | Yes | 도구 호출을 차단합니다 |
PermissionRequest | Yes | 권한을 거부합니다 |
UserPromptSubmit | Yes | 프롬프트 처리를 차단하고 프롬프트를 삭제합니다 |
Stop | Yes | Claude의 중지를 방지하고 대화를 계속합니다 |
SubagentStop | Yes | subagent의 중지를 방지합니다 |
TeammateIdle | Yes | teammate가 유휴 상태로 전환되는 것을 방지합니다 (teammate가 작업을 계속합니다) |
TaskCompleted | Yes | 작업이 완료로 표시되는 것을 방지합니다 |
ConfigChange | Yes | 설정 변경이 적용되는 것을 차단합니다 (policy_settings 제외) |
PostToolUse | No | stderr을 Claude에게 표시합니다 (도구가 이미 실행됨) |
PostToolUseFailure | No | stderr을 Claude에게 표시합니다 (도구가 이미 실패함) |
Notification | No | stderr을 사용자에게만 표시합니다 |
SubagentStart | No | stderr을 사용자에게만 표시합니다 |
SessionStart | No | stderr을 사용자에게만 표시합니다 |
SessionEnd | No | stderr을 사용자에게만 표시합니다 |
PreCompact | No | stderr을 사용자에게만 표시합니다 |
WorktreeCreate | Yes | 0이 아닌 모든 종료 코드는 worktree 생성을 실패시킵니다 |
WorktreeRemove | No | 실패 시 디버그 모드에서만 로그가 기록됩니다 |
InstructionsLoaded | No | 종료 코드가 무시됩니다 |
HTTP 응답 처리
HTTP hook은 종료 코드와 stdout 대신 HTTP 상태 코드와 응답 본문을 사용합니다:
- 본문이 비어 있는 2xx: 성공, 출력 없는 exit code 0과 동일
- 일반 텍스트 본문이 있는 2xx: 성공, 텍스트가 컨텍스트로 추가됨
- JSON 본문이 있는 2xx: 성공, command hook과 동일한 JSON 출력 스키마로 파싱됨
- 2xx가 아닌 상태: 비차단 오류, 실행 계속
- 연결 실패 또는 타임아웃: 비차단 오류, 실행 계속
Command hook과 달리, HTTP hook은 상태 코드만으로는 차단 오류를 전달할 수 없습니다. 도구 호출을 차단하거나 권한을 거부하려면 적절한 decision 필드가 포함된 JSON 본문과 함께 2xx 응답을 반환하세요.
JSON 출력
종료 코드를 사용하면 허용 또는 차단할 수 있지만, JSON 출력은 더 세밀한 제어를 제공합니다. exit code 2로 종료하여 차단하는 대신, exit 0으로 종료하고 JSON 객체를 stdout에 출력할 수 있습니다. Claude Code는 해당 JSON에서 특정 필드를 읽어 차단, 허용, 또는 사용자에게 에스컬레이션하는 결정 제어를 포함한 동작을 제어합니다.
참고: hook당 하나의 접근 방식만 선택해야 하며, 둘 다 사용할 수는 없습니다: 종료 코드만 사용하여 신호를 보내거나, exit 0으로 종료하고 구조화된 제어를 위해 JSON을 출력하세요. Claude Code는 exit 0일 때만 JSON을 처리합니다. exit 2로 종료하면 모든 JSON이 무시됩니다.
hook의 stdout에는 JSON 객체만 포함되어야 합니다. 셸 프로필이 시작 시 텍스트를 출력하면 JSON 파싱에 간섭할 수 있습니다. JSON validation failed 트러블슈팅 가이드를 참조하세요.
JSON 객체는 세 종류의 필드를 지원합니다:
- 범용 필드 (
continue등)는 모든 이벤트에서 작동합니다. 아래 표에 나열되어 있습니다. - **최상위
decision과reason**은 일부 이벤트에서 차단하거나 피드백을 제공하는 데 사용됩니다. - **
hookSpecificOutput**은 더 풍부한 제어가 필요한 이벤트를 위한 중첩 객체입니다. 이벤트 이름으로 설정된hookEventName필드가 필요합니다.
| Field | Default | Description |
|---|---|---|
continue | true | false이면 hook 실행 후 Claude가 처리를 완전히 중지합니다. 이벤트별 decision 필드보다 우선합니다 |
stopReason | none | continue가 false일 때 사용자에게 표시되는 메시지. Claude에게는 표시되지 않습니다 |
suppressOutput | false | true이면 verbose 모드 출력에서 stdout을 숨깁니다 |
systemMessage | none | 사용자에게 표시되는 경고 메시지 |
이벤트 유형에 관계없이 Claude를 완전히 중지하려면:
{ "continue": false, "stopReason": "Build failed, fix errors before continuing" }
결정 제어
모든 이벤트가 JSON을 통한 차단이나 동작 제어를 지원하는 것은 아닙니다. 지원하는 이벤트는 각각 다른 필드 세트를 사용하여 결정을 표현합니다. hook을 작성하기 전에 이 표를 빠른 참조로 사용하세요:
| Events | Decision pattern | Key fields |
|---|---|---|
| UserPromptSubmit, PostToolUse, PostToolUseFailure, Stop, SubagentStop, ConfigChange | 최상위 decision | decision: "block", reason |
| TeammateIdle, TaskCompleted | Exit code 또는 continue: false | Exit code 2는 stderr 피드백과 함께 작업을 차단합니다. JSON {"continue": false, "stopReason": "..."}도 teammate를 완전히 중지하며, Stop hook 동작과 일치합니다 |
| PreToolUse | hookSpecificOutput | permissionDecision (allow/deny/ask), permissionDecisionReason |
| PermissionRequest | hookSpecificOutput | decision.behavior (allow/deny) |
| WorktreeCreate | stdout 경로 | hook이 생성된 worktree의 절대 경로를 출력합니다. 0이 아닌 종료 코드는 생성을 실패시킵니다 |
| WorktreeRemove, Notification, SessionEnd, PreCompact, InstructionsLoaded | 없음 | 결정 제어 없음. 로깅이나 정리 작업 등 부수 효과에 사용됩니다 |
다음은 각 패턴의 실제 사용 예시입니다:
Top-level decision
UserPromptSubmit, PostToolUse, PostToolUseFailure, Stop, SubagentStop, ConfigChange에서 사용됩니다. 유일한 값은 "block"입니다. 작업을 진행하려면 JSON에서 decision을 생략하거나, JSON 없이 exit 0으로 종료하세요:
{
"decision": "block",
"reason": "Test suite must pass before proceeding"
}
PreToolUse
더 풍부한 제어를 위해 hookSpecificOutput을 사용합니다: 허용, 거부, 또는 사용자에게 에스컬레이션할 수 있습니다. 도구 입력을 실행 전에 수정하거나 Claude에 추가 컨텍스트를 주입할 수도 있습니다. 전체 옵션은 PreToolUse 결정 제어를 참조하세요.
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Database writes are not allowed"
}
}
PermissionRequest
hookSpecificOutput을 사용하여 사용자 대신 권한 요청을 허용하거나 거부합니다. 허용할 때는 도구의 입력을 수정하거나 사용자에게 다시 묻지 않도록 권한 규칙을 적용할 수도 있습니다. 전체 옵션은 PermissionRequest 결정 제어를 참조하세요.
{
"hookSpecificOutput": {
"hookEventName": "PermissionRequest",
"decision": {
"behavior": "allow",
"updatedInput": {
"command": "npm run lint"
}
}
}
}
Bash 명령 검증, 프롬프트 필터링, 자동 승인 스크립트를 포함한 확장 예시는 가이드의 자동화할 수 있는 것들과 Bash command validator 참조 구현을 참조하세요.