Claude Code hooks 설명: 설정 구조, 매처(Matchers), 그리고 복사해서 붙여넣을 수 있는 PreToolUse 가드
요약
Claude Code의 훅(hooks) 시스템을 활용하여 세션 및 도구 호출 시점을 제어하는 방법을 설명합니다. 설정 구조, 매처(Matcher) 작성 시 주의할 정규 표현식 규칙, 그리고 결과 보고 방식을 상세히 다룹니다.
핵심 포인트
- 훅은 세션, 턴, 도구 호출 시점에 실행되는 결정론적 명령입니다.
- 설정은 이벤트, 매처 그룹, 핸들러의 3단계 중첩 구조로 이루어집니다.
- 매처는 앵커가 없는 정규 표현식으로 동작하므로 정확한 매칭을 위해 주의가 필요합니다.
- PreToolUse 가드를 통해 위험한 명령(예: rm)을 차단하거나 재구성할 수 있습니다.
Claude Code의 권한 프롬프트(permission prompts)만 사용해 보았다면, 훅(hooks)은 그 다음 단계의 레이어입니다. 훅은 세션의 특정 시점에 실행되는 결정론적인 셸 명령(shell commands, 또는 HTTP 엔드포인트, MCP 도구, 프롬프트, 또는 서브에이전트)이며, 다음에 일어날 일을 **차단, 허용 또는 재구성(block, allow, or reshape)**할 수 있습니다. 이 기능은 강력하지만, 설정 과정에서 사람들이 실수하기 쉬운 몇 가지 까다로운 부분들이 있습니다. 특히 훅이 반환하는 JSON 형식이 변경되어 기억에만 의존하면 틀리기 쉽습니다.
이 포스트에서는 현재의 설정 구조, 훅이 결정을 보고하는 두 가지 방식, 그리고 재귀적인 rm 명령을 차단하는 복사해서 붙여넣을 수 있는 PreToolUse 가드에 대해 살펴봅니다.
3단계 설정 구조
훅은 설정 파일(.claude/settings.json은 프로젝트용, ~/.claude/settings.json은 사용자용)에 저장됩니다. 구조는 3단계의 중첩으로 이루어져 있으며, 이름을 다음과 같이 이해하면 나머지 문서들을 파악하기 쉽습니다:
{
"hooks": {
"PreToolUse": [
...
- 훅 이벤트 (Hook event) (
PreToolUse) — 라이프사이클 시점입니다. 이벤트는 세 가지 주기로 나뉩니다: 세션당 한 번(SessionStart,SessionEnd), 턴당 한 번(UserPromptSubmit,Stop), 그리고 모든 도구 호출 시(PreToolUse,PostToolUse). - 매처 그룹 (Matcher group) (
"matcher": "Bash") — 이벤트가 실행될 _시점_을 결정하는 필터입니다. 도구 이벤트의 경우 매처는tool_name을 대상으로 테스트합니다. - 훅 핸들러 (Hook handler) (내부
hooks배열) — 실제로 실행되는 요소입니다. 다섯 가지 유형이 있습니다:command,http,mcp_tool,prompt, 그리고agent.
if 필드는 권한 규칙(permission-rule) 구문을 사용하여 범위를 더 좁힙니다. 여기서 Bash(rm *)는 "Bash 서브 명령이 rm *와 일치할 때만 이 핸들러를 실행한다"는 의미이므로, npm test에 대해서는 스크립트가 아예 실행되지 않습니다. 이 필드는 정확히 하나의 규칙만 가질 수 있습니다. &&나 || 연산자는 없으므로, 여러 조건을 적용하려면 여러 개의 핸들러를 사용해야 합니다.
매처는 앵커가 없는 정규 표현식입니다 (이 부분에서 실수가 발생합니다)
정규 표현식 문자가 포함된 매처는 JavaScript의 RegExp.prototype.test로 테스트되며, 이는 값의 어느 곳에서든 매칭되면 성공합니다. 따라서:
Edit는NotebookEdit와도 매칭됩니다. 오직Edit도구만을 의미한다면^Edit$라고 작성해야 합니다.- MCP 도구들은
mcp__<server>__<tool>형식으로 나타납니다. 단순히mcp__memory라고만 적으면 정확한 문자열(exact string)로 취급되어 아무것도 매칭되지 않습니다.memory서버의 모든 도구에 대응하려면.*접미사를 붙여mcp__memory__.*와 같이 작성해야 합니다.
이벤트의 모든 발생 시점에 핸들러가 실행되기를 원한다면, 매처(matcher)를 생략하거나 "*"를 사용하세요.
결정을 보고하는 두 가지 방법 — 하나만 선택하세요
이 부분이 가장 혼란스러운 지점입니다. 커맨드 훅(command hook)은 **종료 코드(exit codes)와 표준 출력(stdout)**을 통해 결과를 전달하며, 두 가지 모드 중 _하나_를 선택해야 합니다:
종료 코드만 사용. 종료 코드 0은 성공 및 결정 없음(침묵을 유지하는 것은 호출을 승인하는 것이 아닙니다. 단지 일반적인 권한 흐름이 계속되도록 둘 뿐입니다)을 의미합니다. 종료 코드 2는 차단 에러(blocking error)입니다. Claude Code는 표준 출력(stdout)을 무시하고, 당신의 **표준 에러(stderr)**를 Claude에게 이유로서 다시 전달합니다. PreToolUse의 경우, 종료 코드 2는 도구 호출을 차단합니다.
종료 코드 0과 표준 출력(stdout)의 JSON 사용. 더 세밀한 제어를 위해, 종료 코드 0을 반환하고 JSON 객체를 출력합니다. Claude Code는 종료 코드 0일 때만 JSON을 파싱합니다. 만약 종료 코드 2를 반환하면 모든 JSON은 무시됩니다. 훅당 하나의 접근 방식만 선택해야 하며, 두 가지를 동시에 사용할 수 없습니다.
주의사항: PreToolUse는 더 이상 decision: "block"을 사용하지 않습니다
얼마 전에 훅(hooks)을 배우셨다면, 최상위 레벨의 {"decision": "block"}을 기억하실 수도 있습니다. 이는 PreToolUse에서 더 이상 사용되지 않습니다(deprecated). 이제 이 이벤트는 hookEventName이 필수인 hookSpecificOutput 객체 내부에 결정을 반환하며, 필드명은 permissionDecision이고 네 가지 가능한 값(allow, deny, ask, 또는 defer)을 가집니다. (기존의 "approve"/"block"은 "allow"/"deny"에 매핑됩니다.) PostToolUse나 Stop 같은 다른 이벤트들은 여전히 최상위 레벨의 decision/reason 필드를 사용합니다. 따라서 이벤트마다 형식이 실제로 다르며, PostToolUse 스니펫을 PreToolUse에 복사해서 붙여넣으면 작동하지 않습니다.
다음은 PreToolUse에서 거부(deny)를 수행하는 올바른 현재 형태입니다:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
...
복사해서 붙여넣기: 재귀적 rm 차단
이 내용을 .claude/hooks/block-rm.sh로 저장하고 chmod +x를 통해 실행 권한을 부여하세요. 이 스크립트는 stdin(표준 입력)에서 이벤트 JSON을 읽고, jq를 사용하여 Bash 명령어를 추출하며, 재귀적 rm이 포함된 모든 명령을 거부합니다:
#!/usr/bin/env bash
# PreToolUse hook: 재귀적 rm을 거부하며, 그 외의 경우에는 침묵을 유지합니다.
# PATH에 jq가 설치되어 있어야 합니다.
...
위의 설정을 사용하면, if: "Bash(rm *)" 필터는 스크립트가 rm 명령에 대해서만 실행되도록 의미합니다(관련 없는 호출 시 프로세스 실행을 절약함). 또한 매처(Matcher)는 이를 Bash 범위 내로 제한합니다. Claude가 rm -rf /tmp/build를 시도하면, 훅(Hook)이 거부 JSON을 출력하고, Claude Code는 해당 호출을 차단하며, Claude는 그 이유를 확인하게 됩니다.
경로에 관한 참고 사항: ${CLAUDE_PROJECT_DIR}은 Claude Code에 의해 사용자의 프로젝트 루트로 치환되므로, 호출 시점의 작업 디렉토리가 무엇이든 상관없이 훅이 올바르게 경로를 해석합니다. 만약 프로젝트 경로에 공백이 포함되어 있다면, 핸들러를 exec 형태( args 설정)로 전환하여 각 요소가 셸 인용(shell quoting) 없이 하나의 인자로 전달되도록 하세요.
다음 단계
훅(Hooks)은 결정론적인 가드레일(Guardrail) 계층입니다. 알아두면 좋은 두 가지 인접 개념은 다음과 같습니다:
if필터는 최선 노력(Best-effort) 방식입니다. 절대 실패하여 허용(fail open)되어서는 안 되는 엄격한 허용/거부(allow/deny)가 필요하다면, 훅 대신 권한 시스템(Permission system)을 사용하세요. 문서는 잘못된 형식의 Bash 명령어가 입력될 경우if필터가 실패하여 훅을 그대로 실행하게 된다고 명시하고 있습니다.- 스킬(Skills)과 서브에이전트(Subagents)는 프론트매터(Frontmatter)에 훅을 정의할 수 있으며, 이는 해당 컴포넌트의 수명 주기 내로 범위가 제한됩니다. 스킬을 작성하고 있다면, 트리거 메커니즘은 훅 배선(Wiring)만큼이나 중요합니다. 저는 SKILL.md 형식 및 실제로 트리거되는 스킬을 만드는 방법과, 별도로 명령어가 스킬이 된 지금 커스텀 슬래시 명령어가 작동하는 방식에 대해 작성해 두었습니다.
Claude Code에서 /hooks를 입력하면 각 훅(hook)이 어떤 설정 파일에서 왔는지를 포함하여, 사용자가 구성한 모든 내용을 읽기 전용 브라우저로 열 수 있습니다. 이는 매처(matcher)가 실제로 제대로 매칭되고 있는지 빠르게 확인할 수 있는 방법입니다.
저는 Rulestack입니다 — 저는 Claude Code, Cursor, 그리고 Codex를 위한 즉시 사용 가능한 규칙 및 훅 팩(rule and hook packs)을 제작하고 유지 관리합니다. 각 규칙을 직접 만들기 번거롭다면, 바로 실무에 적용 가능한 가드레일이 포함된 Claude Code Hooks Pack을 이용해 보세요. 유용한 AI 코딩 팁을 Bluesky의 @ai-shop.bsky.social에 게시하고 있으니, 이 내용이 도움이 되었다면 팔로우해 주세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기