Claude Code 훅 설정 방법 및 동작 원리 완벽 정리
Claude Code 훅의 실행 원리와 설정 위치를 상세히 설명합니다. 모델의 동의 없이 실행되는 훅의 특성, 종료 코드 2를 활용한 도구 호출 취소 방법, 그리고 버전 2.1.232 기준의 보안 고려 사항을 포함하여 실무 적용 가이드를 제공합니다.
Claude Code 훅이란 무엇인가
Claude Code 훅은 Claude Code가 자체 수명 주기의 특정 시점에 스스로 실행하는 셸 명령어입니다. 이것이 훅과 규칙 파일의 근본적인 차이점입니다. CLAUDE.md의 지침은 권고 사항이며, 모델은 이를 컨텍스트 내의 다른 모든 정보와 비교하여 판단합니다. 반면 훅은 코드이며, 모델의 동의 여부와 관계없이 실행됩니다. 에이전트가 두 번이나 지시한 포맷터를 계속 건너뛴다면, 더 강력한 지침이 필요한 것이 아니라 훅이 필요한 상황입니다.
이 메커니즘은 단순합니다. 설정 파일의 이벤트 이름 아래에 명령어를 등록하면 됩니다. 해당 이벤트가 발생할 때 Claude Code는 명령어를 실행하고 이벤트 데이터를 표준 입력(stdin)으로 JSON(JavaScript object notation) 형식으로 전달합니다. 명령어는 이 데이터를 읽어 작업을 수행한 뒤 종료 상태를 반환합니다. PreToolUse 훅에서 종료 코드 2를 반환하면 도구 호출이 실행되기 전에 취소되며, 스크립트가 표준 에러(stderr)로 출력한 내용은 모델에게 실패 사유로 전달됩니다.
여기에 언급된 이벤트 이름과 필드 이름은 2026년 8월 기준 릴리스 2.1.232를 바탕으로 확인한 Claude Code 훅 참조 문서를 따릅니다. 이 인터페이스는 빠르게 변경되므로, 본 게시물을 포함한 블로그 글의 JSON을 복사하기 전에 반드시 사용 중인 버전에 맞는 참조 문서를 확인하십시오. 본인의 설정은 claude --version를 사용하여 출력할 수 있습니다.
훅 설정의 위치
훅은 설정 파일 내의 JSON 블록입니다. 훅은 6개의 위치에 저장될 수 있으며, 파일의 범위가 곧 훅의 범위가 됩니다.
~/.claude/settings.json: 사용자 머신의 모든 프로젝트에 적용되며, 다른 사용자에게는 영향을 주지 않습니다..claude/settings.json: 특정 프로젝트에 적용되며, 저장소에 커밋되므로 해당 저장소를 복제하는 모든 사용자에게 훅이 적용됩니다..claude/settings.local.json: 특정 프로젝트에 적용되며, 사용자 머신에서만 유효합니다.- 관리형 정책 설정: 조직 전체에 적용되며, 관리자가 설정합니다.
hooks/hooks.json: 플러그인 내부에 위치하며, 해당 플러그인이 활성화된 동안 동작합니다.- 스킬 또는 서브 에이전트 프런트매터: 해당 구성 요소가 활성화된 동안 동작합니다.
이 파일들에 정의된 훅 항목들은 서로를 덮어쓰지 않고 병합됩니다. 프로젝트 설정 파일은 사용자 설정 파일의 훅을 대체하는 대신 자신의 훅을 추가하므로, 하나의 이벤트에 여러 파일에서 정의된 여러 훅이 포함될 수 있습니다. "disableAllHooks": true 설정을 사용하면 훅을 비활성화할 수 있지만, 관리형 정책 설정에서 정의된 훅은 해당 설정이 관리형 설정 내에서 적용되지 않는 한 계속 실행됩니다.
세션 내에서 /hooks을 실행하면 현재 등록된 모든 훅을 이벤트별로 그룹화하여 나열할 수 있으며, 각 훅의 소스 파일과 매처(matcher) 정보를 확인할 수 있습니다. 이 메뉴는 읽기 전용이므로, 훅을 변경하려면 설정 파일을 직접 수정해야 합니다. 파일 감시자(file watcher)가 변경 사항을 자동으로 감지하므로 일반적으로 재시작은 필요하지 않습니다.
Claude Code 훅 이벤트의 종류
릴리스 2.1.232에는 SessionStart부터 SessionEnd까지 총 31개의 이벤트가 나열되어 있으며, 여기에는 압축(compaction), 서브 에이전트, 작업 트리(worktrees), 설정 파일 관련 이벤트가 포함됩니다. 서버 작업에서는 이 중 일부만 사용합니다.
PreToolUse: 도구 호출이 실행되기 전입니다. 이 훅은 차단(block)이 가능합니다.PostToolUse: 도구 호출이 성공한 후입니다. 실패 시에는PostToolUseFailure이 발생하므로, 모든 결과를 확인해야 하는 훅은 두 이벤트 모두를 처리해야 합니다.PermissionRequest: 도구 호출에 권한 결정이 필요할 때 발생하며, 승인 프롬프트가 나타나는 시점입니다.UserPromptSubmit: 프롬프트를 제출한 후 Claude가 처리하기 전입니다. 이 훅이 stdout으로 출력하는 내용은 모델의 컨텍스트에 추가됩니다.SessionStart및SessionEnd: 세션의 시작과 끝입니다.SessionStart은 압축 작업 후에도 발생하며, 이때 매처(matcher) 값은compact입니다.Stop: Claude가 응답을 완료했을 때입니다. 이는 작업 단위가 아니라 턴(turn)당 한 번 발생합니다.
모든 그룹은 훅의 실행 여부를 결정하는 matcher을 포함합니다. 도구 이벤트의 경우 도구 이름으로 필터링하므로, "Edit|Write"는 파일 편집 시에만 발생합니다. 매처는 대소문자를 구분합니다. 매처를 비워두면 모든 발생 건에 대해 훅이 실행됩니다. MCP(Model Context Protocol) 서버에서 제공하는 도구는 mcp__<server>__<tool>으로 명명되므로, 매처를 "mcp__github__.*"로 설정하면 특정 서버의 도구만 포착하고 나머지는 무시합니다.
Stop 훅을 작성할 때 주의해야 할 함정이 있습니다. 차단 기능을 가진 Stop 훅은 모델을 다시 작업 상태로 돌려보내는데, Claude Code는 8회 연속으로 차단이 발생하면 해당 훅을 무시(override)합니다. 훅 입력에서 stop_hook_active 필드를 읽어 해당 값이 참(true)일 때 0으로 종료하십시오. 그렇지 않으면 훅이 제한 횟수에 도달할 때까지 루프를 돌게 됩니다.
훅이 stdin으로 수신하는 데이터
Claude가 npm test을 실행하려고 할 때, Bash에 있는 PreToolUse 훅은 stdin을 통해 다음 데이터를 읽습니다:
{
"session_id": "abc123",
"cwd": "/home/deploy/myproject",
"hook_event_name": "PreToolUse",
"tool_name": "Bash",
"tool_input": {
"command": "npm test"
}
}모든 이벤트는 session_id, cwd, permission_mode, transcript_path 및 hook_event_name를 포함합니다. 도구 이벤트에는 tool_name, tool_input 및 tool_use_id이 추가됩니다. 다른 이벤트들은 각자의 필드를 가집니다. UserPromptSubmit는 prompt 텍스트를 가져오며, SessionStart은 startup, resume, clear, compact 또는 fork 중 하나인 source를 가져옵니다.
jq은 셸 스크립트 내부에서 이 데이터를 읽는 일반적인 방법이지만, 최소 사양의 서버 이미지에는 포함되어 있지 않습니다. Ubuntu 및 Debian에서는 sudo apt install -y jq를 사용하여 먼저 설치하십시오.
종료 상태가 실행 중인 도구 호출에 미치는 영향
결과는 세 가지입니다.
- 종료 상태 0은 훅이 아무런 이의를 제기하지 않음을 의미합니다.
PreToolUse에서는 이것이 승인과 동일하지 않으며, 일반적인 권한 흐름이 계속 실행됩니다.UserPromptSubmit및SessionStart에서는 stdout이 모델의 컨텍스트에 추가됩니다. - 종료 상태 2는 차단 가능한 이벤트(
PreToolUse포함)에서 작업을 차단하며, stderr이 모델에게 표시되는 이유가 됩니다.PostToolUse와 같이 차단할 수 없는 이벤트에서는 차단이 무시되지만, stderr은 피드백으로 모델에게 전달됩니다. - 기타 모든 종료 코드는 비차단 오류입니다. 작업은 계속 진행됩니다. 트랜스크립트에는
Failed with non-blocking status code:텍스트 뒤에 stderr의 첫 번째 줄을 포함하는 훅 오류 알림이 표시됩니다.
차단하거나 조용히 유지하는 것 이상의 작업을 수행하려면 종료 상태 0을 반환하고 stdout에 JSON 객체를 출력하십시오. PreToolUse 훅은 permissionDecision을 사용하여 결정합니다.
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Database drops go through a migration, not through the agent."
}
}"allow"은 대화형 프롬프트를 건너뛰고, "deny"는 호출을 취소하고 그 이유를 모델에 전송하며, "ask"은 프롬프트를 정상적으로 표시합니다. 훅당 하나의 스타일을 선택하십시오. 종료 상태 2와 stdout의 JSON 결정을 혼합하면 결과를 직접 확인해야 하는 상황이 발생합니다.
여러 훅이 하나의 이벤트와 일치하면 병렬로 실행되며 각 훅은 완료될 때까지 실행됩니다. 한 훅의 deny은 다른 훅을 중단시키지 않으므로, 가드레일 훅이 동일한 호출을 거부하는 동안에도 로깅 훅은 로그를 기록합니다. Claude Code는 이후 응답을 병합하여 거부, 보류, 질문, 허용 순서로 가장 제한적인 결과를 유지합니다.
예제 1: 파괴적인 명령어가 실행되기 전에 차단하기
이 내용을 프로젝트의 .claude/hooks/block-destructive.sh로 저장합니다:
#!/bin/bash
# Deny a Bash tool call whose command matches a banned pattern.
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
for pattern in 'rm -rf /' 'mkfs' 'dd if=' 'DROP TABLE'; do
if printf '%s' "$COMMAND" | grep -qiF -- "$pattern"; then
echo "Blocked by policy: the command matches '$pattern'. A human runs this one." >&2
exit 2
fi
done
exit 0실행 권한을 부여한 뒤 .claude/settings.json의 PreToolUse에 등록합니다:
chmod +x .claude/hooks/block-destructive.sh{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-destructive.sh",
"timeout": 10,
"statusMessage": "Checking the command against policy"
}
]
}
]
}
}훅이 자체 입력값으로 인해 충돌하면 차단 기능이 무력화되므로, 신뢰하기 전에 스크립트를 직접 테스트하십시오:
echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /var/lib/postgresql"}}' \
| .claude/hooks/block-destructive.sh
echo $?stderr에 Blocked by policy: 줄이 출력되고 종료 코드가 2이어야 합니다. ls -la과 같은 무해한 명령어를 입력하면 아무런 출력도 없어야 하며 종료 코드는 0이어야 합니다. 세션에서 거부된 호출은 기록에 이유와 함께 나타나며, 모델은 해당 메시지를 읽고 대응합니다.
이 작업을 수행할 가치가 있는 이유는 한 가지 특성 때문입니다. PreToolUse 훅은 모든 권한 모드에서 권한 모드 확인보다 먼저 실행되므로, bypassPermissions 상태에서도 거부 규칙이 유지됩니다. 이것이 Claude Code auto mode 및 권한 설정과 함께 훅을 사용할 때 유용한 이유입니다. 프롬프트가 제한되어 있더라도 훅은 여전히 작동하기 때문입니다.
이 기능의 한계를 명확히 인지해야 합니다. 명령어 문자열을 패턴 매칭하는 것은 에이전트의 부주의를 방지하는 안전장치일 뿐, 에이전트의 교묘한 우회 시도를 막는 경계선은 아닙니다. 동일한 명령어도 grep가 인식하지 못하는 형태로 작성될 수 있기 때문입니다. 엄격한 규칙은 권한 시스템과 프로세스가 실행되는 계정 수준에서 적용해야 합니다.
예제 2: 편집 후 포맷팅 및 린트 수행
PostToolUse와 Edit|Write 매처를 사용하면 파일 편집 도구 실행 직후에 작업을 수행할 수 있습니다. 다음 내용을 .claude/hooks/after-edit.sh로 저장하십시오:
#!/bin/bash
# Format the edited file, then report lint failures back to the model.
INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
[ -z "$FILE" ] && exit 0
case "$FILE" in
*.py)
ruff format "$FILE" >/dev/null 2>&1
if ! ruff check "$FILE" >&2; then
exit 2
fi
;;
*.sh)
if ! shellcheck "$FILE" >&2; then
exit 2
fi
;;
esac
exit 0{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/after-edit.sh",
"timeout": 60
}
]
}
]
}
}Claude에게 Python 파일에 들여쓰기가 잘못된 함수를 추가하도록 요청한 뒤 해당 파일을 여십시오. 파일이 자동으로 포맷팅되어 돌아올 것입니다. 이는 훅이 정상적으로 작동했다는 증거입니다. 훅이 성공하면 대화창에 아무런 메시지도 표시되지 않기 때문입니다.
여기서 exit 2는 아무것도 되돌리지 않습니다. PostToolUse는 도구가 이미 실행된 후에 작동하므로, 어떤 경우든 편집 내용은 디스크에 저장됩니다. exit 2를 사용하는 이유는 ruff check의 출력을 모델에게 피드백으로 전달하기 위함입니다. 이를 통해 모델은 다음 단계로 넘어가지 않고 방금 발생시킨 오류를 스스로 수정하게 됩니다. 이것이 커밋 시점에 발견하는 린트 실패와 에이전트가 동일한 턴 내에서 수정하는 방식의 차이점입니다.
여기서는 두 가지 매처 제한 사항이 중요합니다. Edit|Write은 셸 명령으로 변경된 파일은 감지하지 못하며, Claude는 Bash을 통해 파일을 자주 작성하므로 이 간극은 실질적인 문제가 될 수 있습니다. 호출 단위의 커버리지를 위해서는 Bash도 매칭하고 스크립트가 git status --porcelain을 사용하여 변경된 파일을 나열하도록 하십시오. 턴 단위의 커버리지를 원한다면 스캔 작업을 Stop 훅에 배치하십시오.
예제 3: 감사(audit)를 위해 모든 도구 호출 기록하기
PostToolUse에 빈 매처(matcher)를 설정하면 모든 도구 호출 시 이벤트가 발생합니다. 기록을 홈 디렉터리의 파일이 아닌 시스템 저널로 전송하면 에이전트의 셸이 해당 로그에 접근할 수 없습니다.
{
"hooks": {
"PostToolUse": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "jq -c '{time: now|todate, session: .session_id, cwd: .cwd, tool: .tool_name, input: .tool_input}' | logger -t claude-code -p local0.info"
}
]
}
]
}
}journalctl -t claude-code -o cat | tail -n 5을 사용하여 기록을 확인합니다. 도구 호출당 하나의 JSON 라인이 출력되며, 가장 최근 호출이 마지막에 표시됩니다. 아무것도 출력되지 않는다면 훅(hook)이 실행되지 않은 것이며, 아래의 문제 해결 섹션에서 원인을 확인할 수 있습니다.
실패한 호출을 기록하려면 PostToolUseFailure 아래에 동일한 블록을 추가하십시오. PostToolUse는 성공 시에만 실행되지만, 보통은 실패한 명령어가 더 중요한 정보를 담고 있기 때문입니다. 홈 디렉터리의 파일에 내용을 추가하는 방식 대신 logger을 사용하는 이유는 소유권 때문입니다. 훅은 에이전트 셸과 동일한 사용자의 권한으로 실행되므로, 사용자가 내용을 추가할 수 있는 파일이라면 해당 사용자가 내용을 삭제(truncate)할 수도 있습니다. 반면 저널은 systemd-journald이 자체 계정으로 기록합니다.
훅 실행 시간 제한
The data behind this chart
[
{
"label": "command, http or mcp_tool hook",
"default_timeout_seconds": 600
},
{
"label": "agent hook",
"default_timeout_seconds": 60
},
{
"label": "prompt hook",
"default_timeout_seconds": 30
},
{
"label": "command hook on UserPromptSubmit",
"default_timeout_seconds": 30
},
{
"label": "command hook on MessageDisplay",
"default_timeout_seconds": 10
},
{
"label": "any hook on SessionEnd",
"default_timeout_seconds": 1.5
}
]명령어 훅은 기본적으로 600초, 즉 10분의 실행 시간을 갖습니다. 일부 이벤트는 이 시간을 엄격하게 제한합니다. SessionEnd 훅은 전체가 1.5초의 예산을 공유하므로 세션 종료 시 수행하는 정리 작업은 신속해야 합니다. 단, 훅에 더 긴 timeout를 설정하면 공유 예산이 최대 60초까지 그에 맞춰 늘어납니다.
제한 시간을 초과한 훅은 취소되며 아무런 결정도 내리지 않습니다. PreToolUse 가드레일의 경우, 이는 차단이 발생하지 않음을 의미하며 도구 호출은 일반적인 권한 흐름을 따라 계속 진행됩니다. 이러한 이유로 가드레일 스크립트는 간결하게 유지하십시오. 로그 전송과 같이 사용자가 기다릴 필요가 없는 느린 작업의 경우, "async": true를 설정하면 훅이 도구 호출을 지연시키지 않고 백그라운드에서 실행됩니다.
훅, 규칙 파일, 스킬 및 MCP 서버
에이전트의 동작을 변경한다는 공통점 때문에 네 가지 개념이 혼동되곤 합니다. 이 중 오직 하나만이 제안 수준을 넘어 강제성을 가집니다.
규칙 파일(CLAUDE.md 또는 .claude/rules/ 하위의 파일)은 모델의 컨텍스트에 로드되는 텍스트입니다. 이는 행동 양식을 형성하지만, 아무것도 강제하지 않습니다. 긴 대화, 방대한 diff, 새로운 사용자 요청이 이어지면 규칙 파일의 한 줄은 무시될 수 있습니다. 이것이 바로 에이전트가 작성된 지침을 무시하는 현상의 일반적인 원인입니다.
스킬은 모델이 관련성이 있다고 판단할 때 로드하는 지침과 스크립트의 폴더입니다. 이 판단 과정이 스킬의 핵심이자 한계이기도 합니다. 여전히 결정은 모델이 내리기 때문입니다. 작동하는 가장 작은 변경 사항을 유도하는 Ponytail과 같은 스킬에서 이 양면성을 확인할 수 있습니다. 스킬은 훅으로는 불가능한 방식으로 전체 작업 접근 방식을 형성하지만, 모델이 로드하기로 선택했을 때만 작동합니다.
MCP(Model Context Protocol) 서버는 모델이 호출할 수 있는 새로운 도구를 제공합니다. 이는 에이전트가 접근할 수 있는 범위를 넓혀줍니다. 하지만 에이전트가 무언가를 강제로 수행하게 만들지는 않으며, 별도의 프로세스로 직접 운영해야 하는 독립적인 작업입니다. 자세한 내용은 VPS에서 MCP 서버 실행하기를 참조하십시오.
훅은 이 네 가지 중 모델의 선택 없이 실행되는 유일한 요소입니다. 선호 사항을 설정하려면 규칙 파일을, 모델이 적용할 때 따라야 할 절차를 정의하려면 스킬을 사용하십시오. 매번 반드시 실행되어야 하거나 절대 발생해서는 안 되는 작업에는 훅을 사용해야 합니다. 스킬이 규칙 파일보다 효과적인 경우를 포함한 더 자세한 비교는 스킬, MCP 및 규칙 파일 비교에서 확인할 수 있습니다.
플러그인은 다섯 번째 메커니즘이 아니라 패키징 방식입니다. 훅과 스킬을 하나의 설치 가능한 단위로 묶어 팀 전체가 동일한 가드레일을 적용할 수 있게 합니다. 자세한 내용은 Claude Code 플러그인 작동 방식을 참조하십시오.
공유 VPS에서의 보안 결정
훅(hook)은 에이전트가 트리거하는 코드이며, Claude Code를 실행한 사용자의 권한으로 동작합니다. 이 코드는 해당 사용자의 환경과 파일 권한을 그대로 상속받습니다. 개인용 노트북에서는 워크플로우의 문제이지만, 에이전트가 무인으로 실행되는 VPS에서는 네 가지 실질적인 측면을 고려해야 하는 보안 문제입니다.
저장소 내의 훅은 직접 작성하지 않은 코드입니다. .claude/settings.json는 커밋되는 대상이므로, 저장소를 복제하고 그 안에서 세션을 시작하면 저장소에 포함되어 있던 훅이 등록될 수 있습니다. Claude Code는 프로젝트 훅을 해당 폴더에 대한 작업 공간 신뢰(workspace trust) 대화 상자 뒤에 배치합니다. 즉, 신뢰를 수락하는 순간이 곧 해당 훅을 실행하기로 결정하는 시점입니다. 먼저 hooks 블록을 읽어보십시오.
훅은 전체 도구 입력을 확인합니다. tool_input을 기록하는 감사 훅은 모든 명령의 인자를 파일에 기록하며, 여기에는 명령줄에 포함된 모든 토큰이 포함될 수 있습니다. 따라서 해당 로그 파일은 비밀 정보와 동일한 수준의 보호가 필요하며, 이는 AI 에이전트가 비밀 정보에 접근하지 못하도록 차단하기라는 더 넓은 문제의 일부입니다.
훅은 모델의 컨텍스트에 내용을 기록할 수 있습니다. SessionStart 또는 UserPromptSubmit 훅이 stdout으로 출력하는 모든 내용은 대화에 추가됩니다. 외부 소스, 이슈 트래커, 로그 파일 등에서 텍스트를 파이프라인으로 가져오는 훅은 사용자가 직접 입력한 것처럼 신뢰할 수 없는 텍스트를 모델에 전달하게 됩니다. 이러한 stdout은 출력물이 아닌 입력물로 취급하십시오.
권한이 실질적인 통제 수단입니다. 에이전트는 필요한 sudo 규칙만을 가진 전용 비권한 사용자 계정으로 실행하십시오. PreToolUse 거부 설정을 사용하는 것이 좋으며, 이는 설계상 최선의 노력(best effort)을 다하는 방식입니다. 참조 문서에서도 if 필터에 대해 동일하게 설명하며, 강력한 거부가 필요할 때는 운영체제의 권한 시스템을 사용하라고 권고합니다. 권한 규칙과 프로세스가 실행되는 계정이야말로 압박 상황에서도 유지되는 핵심 요소입니다.
모든 구성에서 변하지 않는 속성이 하나 있습니다. PreToolUse 훅은 모든 권한 모드에서 권한 모드 검사보다 먼저 실행되므로, deny을 반환하는 훅은 bypassPermissions 상태에서도 도구 실행을 차단합니다. 훅은 권한 규칙이 허용하는 범위를 좁힐 수는 있지만, 넓힐 수는 없습니다.
훅이 실행되지 않는 이유는 무엇입니까?
다음 순서대로 문제를 해결하십시오. 각 단계는 실제로 나타나는 증상을 기준으로 합니다.
/hooks를 실행하여 예상한 이벤트 아래에 훅이 나타나는지 확인하십시오. 메뉴에 훅이 보이지 않는다면 설정 파일에 JSON 문법 오류가 있을 가능성이 큽니다. JSON은 후행 쉼표와 주석을 허용하지 않으며, 파일이 위에서 언급한 6개 위치 중 하나에 존재하지 않을 수도 있습니다.- 매처(matcher)와 도구 이름을 정확히 비교하십시오. 매처는 대소문자를 구분하므로
"bash"은Bash도구와 일치하지 않습니다. - 위 예제 1과 같이 샘플 입력을 사용하여 스크립트를 직접 실행해 보십시오. 예상치 못한 종료 코드가 반환된다면 스크립트의 버그이며, Claude Code는 이를 결정(decision)이 아닌 훅 오류로 보고합니다.
jq: command not found이라는 알림은 해당 시스템에jq가 없음을 의미합니다. 사용자 스크립트에서command not found이 발생한다면 경로가 해결되지 않은 것이므로${CLAUDE_PROJECT_DIR}을 사용하거나 절대 경로를 지정하십시오. 스크립트가 전혀 실행되지 않는다면 실행 권한이 없을 가능성이 높습니다.- 훅이 올바른 JSON을 출력하는데 아무 일도 일어나지 않는 경우입니다. 셸 형식의 훅은
sh -c를 통해 실행되는데, 만약 셸 프로필에서 배너를 출력하도록 설정되어 있다면 해당 배너가 JSON 앞에 추가됩니다. 이로 인해 표준 출력(stdout)이{으로 시작하지 않게 되어, Claude Code는 전체 내용을 일반 텍스트로 간주하고 결정을 무시합니다. 종료 코드 0으로 종료되면 디버그 로그 외에는 어디에도 보고되지 않습니다. 프로필의 모든echo는 대화형 셸에서만 실행되도록 감싸야 합니다. - 여전히 해결되지 않는다면
claude --debug-file /tmp/claude.log로 세션을 시작하고 두 번째 터미널에서tail -f /tmp/claude.log을 실행하십시오. 디버그 로그에는 어떤 훅이 일치했는지, 각 훅이 어떤 종료 코드를 반환했는지, 그리고 표준 출력과 표준 에러로 무엇을 기록했는지가 모두 기록됩니다.
FAQ
Claude Code 훅과 CLAUDE.md 지침의 차이점은 무엇입니까?
CLAUDE.md 지침은 모델의 컨텍스트에 포함된 텍스트이므로 대화 및 현재 요청과 주의를 분산하며, 모델이 이를 비교하여 판단할 수 있습니다. 반면 훅은 Claude Code가 수명 주기의 특정 시점에 실행하는 셸 명령이므로, 모델의 판단과 관계없이 해당 이벤트가 발생할 때마다 실행됩니다. 선호 사항을 설정할 때는 지침을 사용하십시오. 반드시 수행해야 하는 단계나 절대 발생해서는 안 되는 동작에는 훅을 사용하십시오.
Claude Code가 특정 셸 명령을 실행하지 못하게 하려면 어떻게 해야 합니까?
.tool_input.command에서 명령을 읽고, stderr에 이유를 출력한 뒤 2를 반환하는 Bash 매처와 함께 PreToolUse 훅을 등록하십시오. Claude Code는 해당 호출을 취소하고 모델에게 이유를 표시합니다. 이 과정은 권한 모드 확인 이전에 발생하므로 bypassPermissions 모드에서도 차단이 유지됩니다. 명령 문자열에 대한 패턴 매칭은 보안 경계라기보다 가드레일에 가깝습니다. 동일한 명령이라도 패턴이 놓칠 수 있는 형태로 작성될 수 있으므로, 권한 규칙 및 비특권 계정을 사용하여 보완하십시오.
훅이 올바른 JSON을 출력하는데 아무 일도 일어나지 않습니다. 왜 그렇습니까?
가장 흔한 원인은 셸 프로필 설정입니다. args 필드가 없는 훅은 sh -c을 통해 실행되는데, 일부 프로필은 모든 셸 시작 시 배너를 출력하며 이것이 JSON보다 앞서 stdout에 기록됩니다. 출력 내용이 {로 시작하지 않게 되면 Claude Code는 이를 모두 일반 텍스트로 간주하여 결정을 무시하며, 종료 코드 0인 경우 기록에 아무것도 남지 않습니다. 프로필 내의 모든 echo는 대화형 셸 테스트로 감싸고, claude --debug-file /tmp/claude.log의 디버그 로그를 읽어 수정 사항을 확인하십시오.
공유 서버에서 Claude Code 훅을 실행해도 안전합니까?
훅은 Claude Code를 시작한 사용자의 권한으로 실행되며 해당 사용자의 파일 권한을 따르므로, 해당 계정이 할 수 있는 모든 작업을 수행할 수 있습니다. 두 가지 습관으로 대부분의 위험을 방지할 수 있습니다. 첫째, 에이전트를 제한적인 sudo 정책을 가진 전용 비특권 계정으로 실행하십시오. 둘째, 작업 공간 신뢰 대화 상자를 수락하기 전에 저장소의 hooks 블록을 읽으십시오. 프로젝트 훅은 .claude/settings.json 내부에 포함되어 배포되기 때문입니다. 훅을 전혀 실행하지 않으려면 설정 파일에서 "disableAllHooks": true을 설정하십시오.