가이드 ·
Claude Code hooks 예제 4개 — 권한 요청 알림, 위험한 명령 막기, 작업 기록, 브라우저로 보내기
Claude Code hooks를 settings.json에 넣는 법과 바로 복사해 쓰는 예제 4개. 권한을 물을 때 맥 알림 띄우기, rm -rf 같은 명령을 exit 2로 막기, 모든 작업을 파일에 남기기, 오픈소스 어전처럼 브라우저 화면으로 보내기까지 실제로 써 본 설정 그대로.
Claude Code hooks는 정해진 순간(도구 사용 전후, 권한 요청, 답변 끝 등)에 내 명령을 실행하게 하는 설정이다. ~/.claude/settings.json 또는 프로젝트의 .claude/settings.json 의 "hooks" 아래에 이벤트 이름별로 넣는다. 명령은 표준 입력으로 JSON(session_id, cwd, hook_event_name, tool_name, tool_input 등)을 받고, exit 0은 통과, exit 2는 (PreToolUse 등에서) 막기, 그 밖의 코드는 경고만 남긴다. 이 글은 권한 요청 알림, 위험한 명령 차단, 작업 기록, 브라우저 표시의 네 가지 예제를 실제 설정 그대로 보여 준다.
Claude Code 한국어 실전 가이드 · 3/10편
결론부터. hooks는 "Claude Code가 ○○할 때 이 명령을 실행해"라는 설정이다. 설정 파일에 몇 줄만 넣으면 권한을 물을 때 알림을 받고, 위험한 명령을 실행 전에 막고, 모든 작업을 기록으로 남길 수 있다. 아래 예제는 하이아이오가 오픈소스 어전(eojeon)을 만들면서 실제로 쓴 방식이다. 이 글은 AI(Claude)가 썼고, 사실은 Claude Code 공식 hooks 문서와 대조했다.
어디에 넣나
| 파일 | 적용 범위 |
|---|---|
~/.claude/settings.json |
내 모든 프로젝트 |
.claude/settings.json |
이 프로젝트(팀과 공유) |
.claude/settings.local.json |
이 프로젝트, 나만(공유 안 함) |
구조는 항상 같다. hooks → 이벤트 이름 → 묶음 배열(matcher + hooks) → 명령이다.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [{ "type": "command", "command": "~/.claude/hooks/guard.sh", "timeout": 5 }]
}
]
}
}
matcher는 도구 이름 거르개다."Bash","Edit|Write"처럼 쓴다. 빼거나"*"면 전부 해당된다.UserPromptSubmit·Stop처럼 matcher를 무시하는 이벤트도 있다.timeout은 초 단위다. 기본값이 600초로 길어서, 짧게 끝나는 훅은 5초 정도로 줄여 두는 게 안전하다.- 넣은 뒤 Claude Code에서
/hooks를 치면 지금 적용된 훅과 그 출처를 볼 수 있다.
훅이 받는 것, 돌려주는 것
명령은 표준 입력으로 JSON 한 덩어리를 받는다. 공통 필드는 session_id, transcript_path, cwd, permission_mode, hook_event_name이다. 도구 이벤트에는 tool_name, tool_input, tool_use_id가 더 붙고, 서브에이전트에서 나온 이벤트에는 agent_id, agent_type이 붙는다.
| 끝내는 코드 | 뜻 |
|---|---|
exit 0 |
통과. stdout이 JSON 하나면 해석하고, 아니면 그냥 글자로 본다 |
exit 2 |
막기. PreToolUse·UserPromptSubmit 같은 이벤트에서는 그 동작이 멈추고, stderr가 이유로 Claude에게 전달된다 |
그 밖 (exit 1 포함) |
막지 않는다. 경고만 남고 진행된다 |
exit 1은 막지 않는다. 차단용 훅은 반드시 exit 2로 끝내야 한다.
예제 1. 권한을 물을 때 맥 알림 받기
Claude가 "이거 해도 될까요?" 하고 기다리는데 다른 창에 있으면 한참 모른다. PermissionRequest에 알림을 걸면 된다.
{
"hooks": {
"PermissionRequest": [
{ "hooks": [{ "type": "command", "command": "osascript -e 'display notification \"권한 요청이 왔습니다\" with title \"Claude Code\"'", "timeout": 5 }] }
]
}
}
macOS 기준이다. 리눅스는 notify-send, 윈도우는 PowerShell 알림 명령으로 바꾸면 된다.
예제 2. 위험한 명령을 실행 전에 막기
PreToolUse + matcher: "Bash"로 명령을 미리 보고, 걸리면 exit 2로 막는다. jq가 필요하다(brew install jq).
#!/bin/sh
# ~/.claude/hooks/guard.sh (chmod +x 할 것)
cmd=$(jq -r '.tool_input.command // ""')
case "$cmd" in
*"rm -rf /"*|*"git push --force"*|*"git push -f"*)
echo "막았습니다: $cmd — 직접 확인 후 실행하세요" >&2
exit 2 ;;
esac
exit 0
설정은 위 '어디에 넣나'의 예시 그대로다. 막히면 Claude는 stderr 문장을 이유로 받고 다른 방법을 찾는다.
예제 3. 모든 작업을 파일에 남기기
언제 무엇을 했는지 남겨 두면, 나중에 "아까 무슨 명령 돌렸지?"를 바로 찾을 수 있다.
{
"hooks": {
"PreToolUse": [
{ "hooks": [{ "type": "command", "command": "jq -c '{t: now|todate, s: .session_id[0:8], tool: .tool_name, in: (.tool_input.command // .tool_input.file_path // \"\")}' >> ~/.claude/tool-log.jsonl", "timeout": 5 }] }
]
}
}
주의: 명령이나 파일 경로에 비밀값(토큰, 비밀번호)이 섞일 수 있다. 기록 파일은 나만 읽게 두고, 어디에도 올리지 않는다.
예제 4. 브라우저 화면으로 보내기 (어전 방식)
어전은 hooks를 이렇게 쓴다.
claude --settings <임시 settings 파일>로 Claude Code를 띄운다. 그러면 내~/.claude/settings.json은 건드리지 않고, 그 실행에만 훅이 붙는다.- 임시 파일에는 이벤트 13개(
SessionStart,UserPromptSubmit,PreToolUse,PostToolUse,PostToolUseFailure,PermissionRequest,PermissionDenied,SubagentStart,SubagentStop,Notification,Stop,StopFailure,SessionEnd)마다 같은 훅 명령을 건다. - 훅 스크립트는 받은 JSON을 짧게 줄인다(도구 이름, 명령 첫 줄, 상대 경로, 비밀 모양 문자열은
***). 그런 다음127.0.0.1의 로컬 서버로만 보낸다. 1.5초 안에 끝나고 항상exit 0이라 Claude Code를 막지 않는다. - 브라우저가 이 이벤트로 궁궐 장면을 그린다. 서브에이전트 시작은 신하 입궐, 권한 요청은 "윤허를 청하옵니다"로 나온다.
직접 보고 싶으면 npx eojeon-cli demo를 실행하면 된다(Claude 없이 장면만 재생). 실제로 쓰려면 npx eojeon-cli. 코드는 GitHub의 server/hook.js와 server/sanitize.js에 있다.
자주 묻는 질문
훅이 Claude Code를 느리게 하나요?
훅은 그 순간에 실행되고, 끝날 때까지(또는 timeout까지) 기다린다. 기본 timeout은 600초라서, 알림이나 기록처럼 짧은 일은 "timeout": 5처럼 줄여 두는 게 좋다. 어전의 훅은 1.5초 안에 스스로 끝난다.
exit 1로 끝냈는데 왜 안 막히나요?
exit 1은 '막지 않는 오류'다. 동작은 그대로 진행되고 경고만 남는다. 막으려면 exit 2로 끝내고, 이유는 stderr에 쓴다.
내 settings.json을 건드리지 않고 한 번만 써 볼 수 있나요?
된다. 훅만 담은 JSON 파일을 따로 만들고 claude --settings 그파일경로로 실행하면, 그 실행에만 적용된다. 어전이 이 방식을 쓴다. 공식 문서에는 모든 훅을 한 번 끄는 예(claude --settings '{"disableAllHooks": true}')가 나와 있다.
지금 어떤 훅이 걸려 있는지 어떻게 보나요?
Claude Code 안에서 /hooks를 치면 된다. 훅마다 어느 파일(사용자, 프로젝트, 로컬, 플러그인)에서 왔는지까지 보인다.