Claude 코드 후크 참조

hackernews | | 🔬 연구
#claude #command r #hooks #review
원문 출처: hackernews · Genesis Park에서 요약 및 분석

요약

Claude Code의 Hooks는 사용자 정의 명령어, HTTP 엔드포인트 또는 LLM 프롬프트로, 특정 이벤트 시점에 자동으로 실행됩니다. 명령어 후크는 표준 입력을, HTTP 후크는 POST 요청 본문을 통해 JSON 컨텍스트를 전달받으며, 핸들러는 이를 검토하고 작업을 수행하거나 의사결정을 반환할 수 있습니다.

본문

Hooks are user-defined shell commands, HTTP endpoints, or LLM prompts that execute automatically at specific points in Claude Code’s lifecycle. Use this reference to look up event schemas, configuration options, JSON input/output formats, and advanced features like async hooks, HTTP hooks, and MCP tool hooks. If you’re setting up hooks for the first time, start with the guide instead. Hook lifecycle Hooks fire at specific points during a Claude Code session. When an event fires and a matcher matches, Claude Code passes JSON context about the event to your hook handler. For command hooks, input arrives on stdin. For HTTP hooks, it arrives as the POST request body. Your handler can then inspect the input, take action, and optionally return a decision. Events fall into three cadences: once per session (SessionStart , SessionEnd ), once per turn (UserPromptSubmit , Stop , StopFailure ), and on every tool call inside the agentic loop (PreToolUse , PostToolUse ): The table below summarizes when each event fires. The Hook events section documents the full input schema and decision control options for each one. | Event | When it fires | |---| SessionStart | When a session begins or resumes | UserPromptSubmit | When you submit a prompt, before Claude processes it | UserPromptExpansion | When a user-typed command expands into a prompt, before it reaches Claude. Can block the expansion | PreToolUse | Before a tool call executes. Can block it | PermissionRequest | When a permission dialog appears | PermissionDenied | When a tool call is denied by the auto mode classifier. Return {retry: true} to tell the model it may retry the denied tool call | PostToolUse | After a tool call succeeds | PostToolUseFailure | After a tool call fails | PostToolBatch | After a full batch of parallel tool calls resolves, before the next model call | Notification | When Claude Code sends a notification | SubagentStart | When a subagent is spawned | SubagentStop | When a subagent finishes | TaskCreated | When a task is being created via TaskCreate | TaskCompleted | When a task is being marked as completed | Stop | When Claude finishes responding | StopFailure | When the turn ends due to an API error. Output and exit code are ignored | TeammateIdle | When an agent team teammate is about to go idle | InstructionsLoaded | When a CLAUDE.md or .claude/rules/*.md file is loaded into context. Fires at session start and when files are lazily loaded during a session | ConfigChange | When a configuration file changes during a session | CwdChanged | When the working directory changes, for example when Claude executes a cd command. Useful for reactive environment management with tools like direnv | FileChanged | When a watched file changes on disk. The matcher field specifies which filenames to watch | WorktreeCreate | When a worktree is being created via --worktree or isolation: "worktree" . Replaces default git behavior | WorktreeRemove | When a worktree is being removed, either at session exit or when a subagent finishes | PreCompact | Before context compaction | PostCompact | After context compaction completes | Elicitation | When an MCP server requests user input during a tool call | ElicitationResult | After a user responds to an MCP elicitation, before the response is sent back to the server | SessionEnd | When a session terminates | How a hook resolves To see how these pieces fit together, consider this PreToolUse hook that blocks destructive shell commands. The matcher narrows to Bash tool calls and the if condition narrows further to Bash subcommands matching rm * , so block-rm.sh only spawns when both filters match: The script reads the JSON input from stdin, extracts the command, and returns a permissionDecision of "deny" if it contains rm -rf : Now suppose Claude Code decides to run Bash "rm -rf /tmp/build" . Here’s what happens: Event fires The PreToolUse event fires. Claude Code sends the tool input as JSON on stdin to the hook: Matcher checks The matcher "Bash" matches the tool name, so this hook group activates. If you omit the matcher or use "*" , the group activates on every occurrence of the event. If condition checks The if condition "Bash(rm *)" matches because rm -rf /tmp/build is a subcommand matching rm * , so this handler spawns. If the command had been npm test , the if check would fail and block-rm.sh would never run, avoiding the process spawn overhead. The if field is optional; without it, every handler in the matched group runs. Hook handler runs The script inspects the full command and finds rm -rf , so it prints a decision to stdout:If the command had been a safer rm variant like rm file.txt , the script would hit exit 0 instead, which tells Claude Code to allow the tool call with no further action. Claude Code acts on the result Claude Code reads the JSON decision, blocks the tool call, and shows Claude the reason. The Configuration section below documents the full schema, and each hook event section documents what input your command receives and what output it can return. Configuration Hooks are defined in JSON settings files. The configuration has three levels of nesting: - Choose a hook event to respond to, like PreToolUse or Stop - Add a matcher group to filter when it fires, like “only for the Bash tool” - Define one or more hook handlers to run when matched See How a hook resolves above for a complete walkthrough with an annotated example. This page uses specific terms for each level: hook event for the lifecycle point, matcher group for the filter, and hook handler for the shell command, HTTP endpoint, MCP tool, prompt, or agent that runs. “Hook” on its own refers to the general feature. Hook locations Where you define a hook determines its scope: | Location | Scope | Shareable | |---| ~/.claude/settings.json | All your projects | No, local to your machine | .claude/settings.json | Single project | Yes, can be committed to the repo | .claude/settings.local.json | Single project | No, gitignored | | Managed policy settings | Organization-wide | Yes, admin-controlled | Plugin hooks/hooks.json | When plugin is enabled | Yes, bundled with the plugin | | Skill or agent frontmatter | While the component is active | Yes, defined in the component file | For details on settings file resolution, see settings. Enterprise administrators can use allowManagedHooksOnly to block user, project, and plugin hooks. Hooks from plugins force-enabled in managed settings enabledPlugins are exempt, so administrators can distribute vetted hooks through an organization marketplace. See Hook configuration. Matcher patterns The matcher field filters when hooks fire. How a matcher is evaluated depends on the characters it contains: | Matcher value | Evaluated as | Example | |---| "*" , "" , or omitted | Match all | fires on every occurrence of the event | Only letters, digits, _ , and | | Exact string, or | -separated list of exact strings | Bash matches only the Bash tool; Edit|Write matches either tool exactly | | Contains any other character | JavaScript regular expression | ^Notebook matches any tool starting with Notebook; mcp__memory__.* matches every tool from the memory server | The FileChanged event does not follow these rules when building its watch list. See FileChanged. Each event type matches on a different field: | Event | What the matcher filters | Example matcher values | |---| PreToolUse , PostToolUse , PostToolUseFailure , PermissionRequest , PermissionDenied | tool name | Bash , Edit|Write , mcp__.* | SessionStart | how the session started | startup , resume , clear , compact | SessionEnd | why the session ended | clear , resume , logout , prompt_input_exit , bypass_permissions_disabled , other | Notification | notification type | permission_prompt , idle_prompt , auth_success , elicitation_dialog | SubagentStart | agent type | Bash , Explore , Plan , or custom agent names | PreCompact , PostCompact | what triggered compaction | manual , auto | SubagentStop | agent type | same values as SubagentStart | ConfigChange | configuration source | user_settings , project_settings , local_settings , policy_settings , skills | CwdChanged | no matcher support | always fires on every directory change | FileChanged | literal filenames to watch (see FileChanged) | .envrc|.env | StopFailure | error type | rate_limit , authentication_failed , billing_error , invalid_request , server_error , max_output_tokens , unknown | InstructionsLoaded | load reason | session_start , nested_traversal , path_glob_match , include , compact | UserPromptExpansion | command name | your skill or command names | Elicitation | MCP server name | your configured MCP server names | ElicitationResult | MCP server name | same values as Elicitation | UserPromptSubmit , PostToolBatch , Stop , TeammateIdle , TaskCreated , TaskCompleted , WorktreeCreate , WorktreeRemove | no matcher support | always fires on every occurrence | The matcher runs against a field from the JSON input that Claude Code sends to your hook on stdin. For tool events, that field is tool_name . Each hook event section lists the full set of matcher values and the input schema for that event. This example runs a linting script only when Claude writes or edits a file: UserPromptSubmit , PostToolBatch , Stop , TeammateIdle , TaskCreated , TaskCompleted , WorktreeCreate , WorktreeRemove , and CwdChanged don’t support matchers and always fire on every occurrence. If you add a matcher field to these events, it is silently ignored. For tool events, you can filter more narrowly by setting the if field on individual hook handlers. if uses permission rule syntax to match against the tool name and arguments together, so "Bash(git *)" runs when any subcommand of the Bash input matches git * and "Edit(*.ts)" runs only for TypeScript files. MCP server tools appear as regular tools in tool events (PreToolUse , PostToolUse , PostToolUseFailure , PermissionRequest , PermissionDenied ), so you can match them the same way you match any other tool name. MCP tools follow the naming pattern mcp____ , for example: mcp__memory__create_entities : Memory server’s create entities tool mcp__filesystem__read_file : Filesystem server’s read file tool mcp__github__search_repositories : GitHub server’s search tool To match every tool from a server, append .* to the server prefix. The .* is required: a matcher like mcp__memory contains only letters and underscores, so it is compared as an exact string and matches no tool. mcp__memory__.* matches all tools from the memory server mcp__.*__write.* matches any tool whose name starts with write from any server This example logs all memory server operations and validates write operations from any MCP server: Hook handler fields Each object in the inner hooks array is a hook handler: the shell command, HTTP endpoint, MCP tool, LLM prompt, or agent that runs when the matcher matches. There are five types: - Command hooks ( type: "command" ): run a shell command. Your script receives the event’s JSON input on stdin and communicates results back through exit codes and stdout. - HTTP hooks ( type: "http" ): send the event’s JSON input as an HTTP POST request to a URL. The endpoint communicates results back through the response body using the same JSON output format as command hooks. - MCP tool hooks ( type: "mcp_tool" ): call a tool on an already-connected MCP server. The tool’s text output is treated like command-hook stdout. - Prompt hooks ( type: "prompt" ): send a prompt to a Claude model for single-turn evaluation. The model returns a yes/no decision as JSON. See Prompt-based hooks. - Agent hooks ( type: "agent" ): spawn a subagent that can use tools like Read, Grep, and Glob to verify conditions before returning a decision. Agent hooks are experimental and may change. See Agent-based hooks. Common fields These fields apply to all hook types: | Field | Required | Description | |---| type | yes | "command" , "http" , "mcp_tool" , "prompt" , or "agent" | if | no | Permission rule syntax to filter when this hook runs, such as "Bash(git *)" or "Edit(*.ts)" . The hook only spawns if the tool call matches the pattern, or if a Bash command is too complex to parse. Only evaluated on tool events: PreToolUse , PostToolUse , PostToolUseFailure , PermissionRequest , and PermissionDenied . On other events, a hook with if set never runs. Uses the same syntax as permission rules | timeout | no | Seconds before canceling. Defaults: 600 for command, 30 for prompt, 60 for agent | statusMessage | no | Custom spinner message displayed while the hook runs | once | no | If true , runs once per session then is removed. Only honored for hooks declared in skill frontmatter; ignored in settings files and agent frontmatter | The if field holds exactly one permission rule. There is no && , || , or list syntax for combining rules; to apply multiple conditions, define a separate hook handler for each. For Bash, the rule is matched against each subcommand of the tool input after leading VAR=value assignments are stripped, so if: "Bash(git push *)" matches both FOO=bar git push and npm test && git push . The hook runs if any subcommand matches, and always runs when the command is too complex to parse. Command hook fields In addition to the common fields, command hooks accept these fields: | Field | Required | Description | |---| command | yes | Shell command to execute | async | no | If true , runs in the background without blocking. See Run hooks in the background | asyncRewake | no | If true , runs in the background and wakes Claude on exit code 2. Implies async . The hook’s stderr, or stdout if stderr is empty, is shown to Claude as a system reminder so it can react to a long-running background failure | shell | no | Shell to use for this hook. Accepts "bash" (default) or "powershell" . Setting "powershell" runs the command via PowerShell on Windows. Does not require CLAUDE_CODE_USE_POWERSHELL_TOOL since hooks spawn PowerShell directly | HTTP hook fields In addition to the common fields, HTTP hooks accept these fields: | Field | Required | Description | |---| url | yes | URL to send the POST request to | headers | no | Additional HTTP headers as key-value pairs. Values support environment variable interpolation using $VAR_NAME or ${VAR_NAME} syntax. Only variables listed in allowedEnvVars are resolved | allowedEnvVars | no | List of environment variable names that may be interpolated into header values. References to unlisted variables are replaced with empty strings. Required for any env var interpolation to work | Claude Code sends the hook’s JSON input as the POST request body with Content-Type: application/json . The response body uses the same JSON output format as command hooks. Error handling differs from command hooks: non-2xx responses, connection failures, and timeouts all produce non-blocking errors that allow execution to continue. To block a tool call or deny a permission, return a 2xx response with a JSON

Genesis Park 편집팀이 AI를 활용하여 작성한 분석입니다. 원문은 출처 링크를 통해 확인할 수 있습니다.

공유

관련 저널 읽기

전체 보기 →