通俗版 Claude Code 文档

结构照原 docs,内容改成真能照着做的人话版。

查看原始文档 目录顺序与官方保持一致

参考

Hooks 参考

Hooks 的参考表,查配置和字段用。

页面信息

对应原页

Hooks reference

页面性质

第三方中文解释页

使用建议

先看人话解释,再对照原页命令和代码

这页不是官方原文,而是顺着官方文档结构做的中文解释版。命令、参数、配置名这些硬东西尽量保留,解释部分则尽量讲成人能照着做的话。

如果你碰到特别敏感的配置、权限或企业环境差异,最好顺手点上面的“查看原始文档”再核一遍。

这一页先讲明白

这页是 Hooks 的查表页,重点是你要知道有哪些字段和规则可用。

适合真正要写 hooks 配置时翻着用。

它像门禁规则说明书,不一定天天读,但到真要配的时候特别关键。

这一页更偏参考手册,不是闲聊型内容。

你需要的是准确,不是花哨。

写 hooks 前先把触发时机和输入输出想清楚。

先从最简单、最稳的钩子开始,不要一上来写一大串复杂逻辑。

查字段和例子时,这页最好和 hooks-guide 一起看。

改配置

原页关键片段:How a hook resolves 1

这一段说完,最后还得写到配置里才算真的生效。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "if": "Bash(rm *)",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-rm.sh"
          }
        ]
      }
    ]
  }
}
终端里敲

原页关键片段:How a hook resolves 2

这一段不是只让你理解意思,下面这条命令就是现在要跑的。

#!/bin/bash
# .claude/hooks/block-rm.sh
COMMAND=$(jq -r '.tool_input.command')

if echo "$COMMAND" | grep -q 'rm -rf'; then
  jq -n '{
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      permissionDecision: "deny",
      permissionDecisionReason: "Destructive command blocked by hook"
    }
  }'
else
  exit 0  # allow the command
fi
改配置

原页关键片段:How a hook resolves 3

这一段说完,最后还得写到配置里才算真的生效。

{ "tool_name": "Bash", "tool_input": { "command": "rm -rf /tmp/build" }, ... }
改配置

原页关键片段:Matcher patterns 1

想把这条规矩固定住,就把下面这块老老实实写进去。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/lint-check.sh"
          }
        ]
      }
    ]
  }
}
改配置

原页关键片段:Matcher patterns 2

想把这条规矩固定住,就把下面这块老老实实写进去。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "mcp__memory__.*",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'Memory operation initiated' >> ~/mcp-operations.log"
          }
        ]
      },
      {
        "matcher": "mcp__.*__write.*",
        "hooks": [
          {
            "type": "command",
            "command": "/home/user/scripts/validate-mcp-write.py"
          }
        ]
      }
    ]
  }
}
改配置

原页关键片段:Hook handler fields 1

光知道意思还不够,这里得把规矩落进配置里,下面这块照着填。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "http",
            "url": "http://localhost:8080/hooks/pre-tool-use",
            "timeout": 30,
            "headers": {
              "Authorization": "Bearer $MY_TOKEN"
            },
            "allowedEnvVars": ["MY_TOKEN"]
          }
        ]
      }
    ]
  }
}
改配置

原页关键片段:Hook handler fields 2

光知道意思还不够,这里得把规矩落进配置里,下面这块照着填。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "mcp_tool",
            "server": "my_server",
            "tool": "security_scan",
            "input": { "file_path": "${tool_input.file_path}" }
          }
        ]
      }
    ]
  }
}
改配置

原页关键片段:Reference scripts by path 1

这一段说完,最后还得写到配置里才算真的生效。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-style.sh"
          }
        ]
      }
    ]
  }
}
改配置

原页关键片段:Reference scripts by path 2

这一段说完,最后还得写到配置里才算真的生效。

{
  "description": "Automatic code formatting",
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PLUGIN_ROOT}/scripts/format.sh",
            "timeout": 30
          }
        ]
      }
    ]
  }
}
改配置

原页关键片段:Hooks in skills and agents

光知道意思还不够,这里得把规矩落进配置里,下面这块照着填。

---
name: secure-operations
description: Perform operations with security checks
hooks:
  PreToolUse:
    - matcher: "Bash"
      hooks:
        - type: command
          command: "./scripts/security-check.sh"
---
改配置

原页关键片段:Common input fields

这会儿轮到改配置了,字段名和关键字别自己乱换。

{
  "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"
  }
}
终端里敲

原页关键片段:Exit code output

真到动手的时候了,下面这条直接敲一遍,看它回什么。

#!/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

预留广告位

正文中段响应式广告 等你后面真接 AdSense,这里再放正式广告。

Documentation Index

这里不是让你背"Documentation Index"这个词,而是让你看它真干活时怎么使。

这里还牵扯作用域,意思就是这条规则到底管当前项目、你个人,还是只管这一趟会话。

Hook lifecycle

这一块主要是在说"Hook lifecycle"真到手上该怎么用,哪里最容易踩坑。

这里还牵扯作用域,意思就是这条规则到底管当前项目、你个人,还是只管这一趟会话。

How a hook resolves

看到这里,就把"How a hook resolves"当成一件真要上手的活来看。

看这段时要特别盯工具和权限边界,别为了省事一把全开。

改配置

How a hook resolves 1

这一段说完,最后还得写到配置里才算真的生效。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "if": "Bash(rm *)",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-rm.sh"
          }
        ]
      }
    ]
  }
}
终端里敲

How a hook resolves 2

这一段不是只让你理解意思,下面这条命令就是现在要跑的。

#!/bin/bash
# .claude/hooks/block-rm.sh
COMMAND=$(jq -r '.tool_input.command')

if echo "$COMMAND" | grep -q 'rm -rf'; then
  jq -n '{
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      permissionDecision: "deny",
      permissionDecisionReason: "Destructive command blocked by hook"
    }
  }'
else
  exit 0  # allow the command
fi
改配置

How a hook resolves 3

这一段说完,最后还得写到配置里才算真的生效。

{ "tool_name": "Bash", "tool_input": { "command": "rm -rf /tmp/build" }, ... }
改配置

How a hook resolves 4

这一段说完,最后还得写到配置里才算真的生效。

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Destructive command blocked by hook"
  }
}

Configuration

这一段主要是在把"Configuration"讲实,不是只摆个标题给你看。

看这段时要特别盯工具和权限边界,别为了省事一把全开。

Hook locations

这一块主要是在说"Hook locations"真到手上该怎么用,哪里最容易踩坑。

这里还牵扯作用域,意思就是这条规则到底管当前项目、你个人,还是只管这一趟会话。

Matcher patterns

这一段不只是挂个标题,它是在说明"Matcher patterns"这一块到底负责什么。

如果这里反复提 lead 和 teammate,通常是在提醒主代理别抢队友还没做完的活,先等人把结果交回来再继续统筹。

改配置

Matcher patterns 1

想把这条规矩固定住,就把下面这块老老实实写进去。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/lint-check.sh"
          }
        ]
      }
    ]
  }
}
改配置

Matcher patterns 2

想把这条规矩固定住,就把下面这块老老实实写进去。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "mcp__memory__.*",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'Memory operation initiated' >> ~/mcp-operations.log"
          }
        ]
      },
      {
        "matcher": "mcp__.*__write.*",
        "hooks": [
          {
            "type": "command",
            "command": "/home/user/scripts/validate-mcp-write.py"
          }
        ]
      }
    ]
  }
}

Hook handler fields

这种内容最怕想当然,老老实实照它列的格式走最稳。

看这段时要特别盯工具和权限边界,别为了省事一把全开。

改配置

Hook handler fields 1

光知道意思还不够,这里得把规矩落进配置里,下面这块照着填。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "http",
            "url": "http://localhost:8080/hooks/pre-tool-use",
            "timeout": 30,
            "headers": {
              "Authorization": "Bearer $MY_TOKEN"
            },
            "allowedEnvVars": ["MY_TOKEN"]
          }
        ]
      }
    ]
  }
}
改配置

Hook handler fields 2

光知道意思还不够,这里得把规矩落进配置里,下面这块照着填。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "mcp_tool",
            "server": "my_server",
            "tool": "security_scan",
            "input": { "file_path": "${tool_input.file_path}" }
          }
        ]
      }
    ]
  }
}

Reference scripts by path

看到这里,就别靠猜了,字段、格式和写法都得按说明来。

这里还牵扯作用域,意思就是这条规则到底管当前项目、你个人,还是只管这一趟会话。

改配置

Reference scripts by path 1

这一段说完,最后还得写到配置里才算真的生效。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/check-style.sh"
          }
        ]
      }
    ]
  }
}
改配置

Reference scripts by path 2

这一段说完,最后还得写到配置里才算真的生效。

{
  "description": "Automatic code formatting",
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PLUGIN_ROOT}/scripts/format.sh",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

Hooks in skills and agents

这里不是让你背"Hooks in skills and agents"这个词,而是让你看它真干活时怎么使。

这里还牵扯作用域,意思就是这条规则到底管当前项目、你个人,还是只管这一趟会话。

改配置

Hooks in skills and agents

光知道意思还不够,这里得把规矩落进配置里,下面这块照着填。

---
name: secure-operations
description: Perform operations with security checks
hooks:
  PreToolUse:
    - matcher: "Bash"
      hooks:
        - type: command
          command: "./scripts/security-check.sh"
---

The /hooks menu

这一块主要是在说"The /hooks menu"真到手上该怎么用,哪里最容易踩坑。

这里还牵扯作用域,意思就是这条规则到底管当前项目、你个人,还是只管这一趟会话。

Disable or remove hooks

这里讲怎么把某个开关拧对。重点不是概念,而是你该在哪儿改、改完怎么确认生效。

这里还牵扯作用域,意思就是这条规则到底管当前项目、你个人,还是只管这一趟会话。

Hook input and output

这一块主要是在说"Hook input and output"真到手上该怎么用,哪里最容易踩坑。

如果你打算把外接能力往里挂,这里提到的 hooks、MCP、skills、memory 都要分清各自负责哪一摊。

Common input fields

这种内容最怕想当然,老老实实照它列的格式走最稳。

看这段时要特别盯工具和权限边界,别为了省事一把全开。

改配置

Common input fields

这会儿轮到改配置了,字段名和关键字别自己乱换。

{
  "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"
  }
}

Exit code output

这里主要是在交代"Exit code output"这一块会碰到哪些事。

这里还牵扯作用域,意思就是这条规则到底管当前项目、你个人,还是只管这一趟会话。

终端里敲

Exit code output

真到动手的时候了,下面这条直接敲一遍,看它回什么。

#!/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

HTTP response handling

这一段主要是在把"HTTP response handling"讲实,不是只摆个标题给你看。

看这段时要特别盯工具和权限边界,别为了省事一把全开。

JSON output

看到这里,就把"JSON output"当成一件真要上手的活来看。

这里还牵扯作用域,意思就是这条规则到底管当前项目、你个人,还是只管这一趟会话。

改配置

JSON output 1

这一段说完,最后还得写到配置里才算真的生效。

{ "continue": false, "stopReason": "Build failed, fix errors before continuing" }
改配置

JSON output 2

这一段说完,最后还得写到配置里才算真的生效。

{
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "additionalContext": "This file is generated. Edit src/schema.ts and run `bun generate` instead."
  }
}
改配置

JSON output 3

这一段说完,最后还得写到配置里才算真的生效。

{
  "decision": "block",
  "reason": "Test suite must pass before proceeding"
}
改配置

JSON output 4

这一段说完,最后还得写到配置里才算真的生效。

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Database writes are not allowed"
  }
}

Hook events

这里不是让你背"Hook events"这个词,而是让你看它真干活时怎么使。

这里还牵扯作用域,意思就是这条规则到底管当前项目、你个人,还是只管这一趟会话。

SessionStart

这里不是让你背"SessionStart"这个词,而是让你看它真干活时怎么使。

这里还牵扯作用域,意思就是这条规则到底管当前项目、你个人,还是只管这一趟会话。

改配置

SessionStart 1

光知道意思还不够,这里得把规矩落进配置里,下面这块照着填。

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "SessionStart",
  "source": "startup",
  "model": "claude-sonnet-4-6"
}
改配置

SessionStart 2

光知道意思还不够,这里得把规矩落进配置里,下面这块照着填。

{
  "hookSpecificOutput": {
    "hookEventName": "SessionStart",
    "additionalContext": "Current branch: feat/auth-refactor\nUncommitted changes: src/auth.ts, src/login.tsx\nActive issue: #4211 Migrate to OAuth2"
  }
}
终端里敲

SessionStart 3

真到动手的时候了,下面这条直接敲一遍,看它回什么。

#!/bin/bash

if [ -n "$CLAUDE_ENV_FILE" ]; then
  echo 'export NODE_ENV=production' >> "$CLAUDE_ENV_FILE"
  echo 'export DEBUG_LOG=true' >> "$CLAUDE_ENV_FILE"
  echo 'export PATH="$PATH:./node_modules/.bin"' >> "$CLAUDE_ENV_FILE"
fi

exit 0
终端里敲

SessionStart 4

真到动手的时候了,下面这条直接敲一遍,看它回什么。

#!/bin/bash

ENV_BEFORE=$(export -p | sort)

# Run your setup commands that modify the environment
source ~/.nvm/nvm.sh
nvm use 20

if [ -n "$CLAUDE_ENV_FILE" ]; then
  ENV_AFTER=$(export -p | sort)
  comm -13 <(echo "$ENV_BEFORE") <(echo "$ENV_AFTER") >> "$CLAUDE_ENV_FILE"
fi

exit 0

Setup

这一块主要是在说"Setup"真到手上该怎么用,哪里最容易踩坑。

这里还牵扯作用域,意思就是这条规则到底管当前项目、你个人,还是只管这一趟会话。

改配置

Setup 1

想把这条规矩固定住,就把下面这块老老实实写进去。

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "Setup",
  "trigger": "init"
}
改配置

Setup 2

想把这条规矩固定住,就把下面这块老老实实写进去。

{
  "hookSpecificOutput": {
    "hookEventName": "Setup",
    "additionalContext": "Dependencies installed: node_modules, .venv"
  }
}

InstructionsLoaded

这里不是让你背"InstructionsLoaded"这个词,而是让你看它真干活时怎么使。

这里还牵扯作用域,意思就是这条规则到底管当前项目、你个人,还是只管这一趟会话。

改配置

InstructionsLoaded

光知道意思还不够,这里得把规矩落进配置里,下面这块照着填。

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../transcript.jsonl",
  "cwd": "/Users/my-project",
  "hook_event_name": "InstructionsLoaded",
  "file_path": "/Users/my-project/CLAUDE.md",
  "memory_type": "Project",
  "load_reason": "session_start"
}

UserPromptSubmit

这一块主要是在说"UserPromptSubmit"真到手上该怎么用,哪里最容易踩坑。

这里还牵扯作用域,意思就是这条规则到底管当前项目、你个人,还是只管这一趟会话。

改配置

UserPromptSubmit 1

想把这条规矩固定住,就把下面这块老老实实写进去。

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "UserPromptSubmit",
  "prompt": "Write a function to calculate the factorial of a number"
}
改配置

UserPromptSubmit 2

想把这条规矩固定住,就把下面这块老老实实写进去。

{
  "decision": "block",
  "reason": "Explanation for decision",
  "hookSpecificOutput": {
    "hookEventName": "UserPromptSubmit",
    "additionalContext": "My additional context here",
    "sessionTitle": "My session title"
  }
}

UserPromptExpansion

这里不是让你背"UserPromptExpansion"这个词,而是让你看它真干活时怎么使。

这里还牵扯作用域,意思就是这条规则到底管当前项目、你个人,还是只管这一趟会话。

改配置

UserPromptExpansion 1

光知道意思还不够,这里得把规矩落进配置里,下面这块照着填。

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../00893aaf.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "UserPromptExpansion",
  "expansion_type": "slash_command",
  "command_name": "example-skill",
  "command_args": "arg1 arg2",
  "command_source": "plugin",
  "prompt": "/example-skill arg1 arg2"
}
改配置

UserPromptExpansion 2

光知道意思还不够,这里得把规矩落进配置里,下面这块照着填。

{
  "decision": "block",
  "reason": "This slash command is not available",
  "hookSpecificOutput": {
    "hookEventName": "UserPromptExpansion",
    "additionalContext": "Additional context for this expansion"
  }
}

PreToolUse

这里不是让你背"PreToolUse"这个词,而是让你看它真干活时怎么使。

这里还牵扯作用域,意思就是这条规则到底管当前项目、你个人,还是只管这一趟会话。

改配置

PreToolUse 1

光知道意思还不够,这里得把规矩落进配置里,下面这块照着填。

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow",
    "permissionDecisionReason": "My reason here",
    "updatedInput": {
      "field_to_modify": "new value"
    },
    "additionalContext": "Current environment: production. Proceed with caution."
  }
}
改配置

PreToolUse 2

光知道意思还不够,这里得把规矩落进配置里,下面这块照着填。

{
  "type": "result",
  "subtype": "success",
  "stop_reason": "tool_deferred",
  "session_id": "abc123",
  "deferred_tool_use": {
    "id": "toolu_01abc",
    "name": "AskUserQuestion",
    "input": { "questions": [{ "question": "Which framework?", "header": "Framework", "options": [{"label": "React"}, {"label": "Vue"}], "multiSelect": false }] }
  }
}

PermissionRequest

这里不是让你背"PermissionRequest"这个词,而是让你看它真干活时怎么使。

这里还牵扯作用域,意思就是这条规则到底管当前项目、你个人,还是只管这一趟会话。

改配置

PermissionRequest 1

光知道意思还不够,这里得把规矩落进配置里,下面这块照着填。

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "PermissionRequest",
  "tool_name": "Bash",
  "tool_input": {
    "command": "rm -rf node_modules",
    "description": "Remove node_modules directory"
  },
  "permission_suggestions": [
    {
      "type": "addRules",
      "rules": [{ "toolName": "Bash", "ruleContent": "rm -rf node_modules" }],
      "behavior": "allow",
      "destination": "localSettings"
    }
  ]
}
改配置

PermissionRequest 2

光知道意思还不够,这里得把规矩落进配置里,下面这块照着填。

{
  "hookSpecificOutput": {
    "hookEventName": "PermissionRequest",
    "decision": {
      "behavior": "allow",
      "updatedInput": {
        "command": "npm run lint"
      }
    }
  }
}

PostToolUse

这一块主要是在说"PostToolUse"真到手上该怎么用,哪里最容易踩坑。

看这段时要特别盯工具和权限边界,别为了省事一把全开。

改配置

PostToolUse 1

想把这条规矩固定住,就把下面这块老老实实写进去。

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "PostToolUse",
  "tool_name": "Write",
  "tool_input": {
    "file_path": "/path/to/file.txt",
    "content": "file content"
  },
  "tool_response": {
    "filePath": "/path/to/file.txt",
    "success": true
  },
  "tool_use_id": "toolu_01ABC123...",
  "duration_ms": 12
}
改配置

PostToolUse 2

想把这条规矩固定住,就把下面这块老老实实写进去。

{
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "additionalContext": "Additional information for Claude",
    "updatedToolOutput": {
      "stdout": "[redacted]",
      "stderr": "",
      "interrupted": false,
      "isImage": false
    }
  }
}

PostToolUseFailure

这一块主要是在说"PostToolUseFailure"真到手上该怎么用,哪里最容易踩坑。

看这段时要特别盯工具和权限边界,别为了省事一把全开。

改配置

PostToolUseFailure 1

想把这条规矩固定住,就把下面这块老老实实写进去。

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "PostToolUseFailure",
  "tool_name": "Bash",
  "tool_input": {
    "command": "npm test",
    "description": "Run test suite"
  },
  "tool_use_id": "toolu_01ABC123...",
  "error": "Command exited with non-zero status code 1",
  "is_interrupt": false,
  "duration_ms": 4187
}
改配置

PostToolUseFailure 2

想把这条规矩固定住,就把下面这块老老实实写进去。

{
  "hookSpecificOutput": {
    "hookEventName": "PostToolUseFailure",
    "additionalContext": "Additional information about the failure for Claude"
  }
}

PostToolBatch

这里不是让你背"PostToolBatch"这个词,而是让你看它真干活时怎么使。

看这段时要特别盯工具和权限边界,别为了省事一把全开。

改配置

PostToolBatch 1

光知道意思还不够,这里得把规矩落进配置里,下面这块照着填。

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "PostToolBatch",
  "tool_calls": [
    {
      "tool_name": "Read",
      "tool_input": {"file_path": "/.../ledger/accounts.py"},
      "tool_use_id": "toolu_01...",
      "tool_response": "     1\tfrom __future__ import annotations\n     2\t..."
    },
    {
      "tool_name": "Read",
      "tool_input": {"file_path": "/.../ledger/transactions.py"},
      "tool_use_id": "toolu_02...",
      "tool_response": "     1\tfrom __future__ import annotations\n     2\t..."
    }
  ]
}
改配置

PostToolBatch 2

光知道意思还不够,这里得把规矩落进配置里,下面这块照着填。

{
  "hookSpecificOutput": {
    "hookEventName": "PostToolBatch",
    "additionalContext": "These files are part of the ledger module. Run pytest before marking the task complete."
  }
}

PermissionDenied

这里不是让你背"PermissionDenied"这个词,而是让你看它真干活时怎么使。

看这段时要特别盯工具和权限边界,别为了省事一把全开。

改配置

PermissionDenied 1

光知道意思还不够,这里得把规矩落进配置里,下面这块照着填。

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "auto",
  "hook_event_name": "PermissionDenied",
  "tool_name": "Bash",
  "tool_input": {
    "command": "rm -rf /tmp/build",
    "description": "Clean build directory"
  },
  "tool_use_id": "toolu_01ABC123...",
  "reason": "Auto mode denied: command targets a path outside the project"
}
改配置

PermissionDenied 2

光知道意思还不够,这里得把规矩落进配置里,下面这块照着填。

{
  "hookSpecificOutput": {
    "hookEventName": "PermissionDenied",
    "retry": true
  }
}

Notification

看到这里,就把"Notification"当成一件真要上手的活来看。

看这段时要特别盯工具和权限边界,别为了省事一把全开。

改配置

Notification 1

这一段说完,最后还得写到配置里才算真的生效。

{
  "hooks": {
    "Notification": [
      {
        "matcher": "permission_prompt",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/permission-alert.sh"
          }
        ]
      },
      {
        "matcher": "idle_prompt",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/idle-notification.sh"
          }
        ]
      }
    ]
  }
}
改配置

Notification 2

这一段说完,最后还得写到配置里才算真的生效。

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "Notification",
  "message": "Claude needs your permission to use Bash",
  "title": "Permission needed",
  "notification_type": "permission_prompt"
}

SubagentStart

看到这里,就把"SubagentStart"当成一件真要上手的活来看。

看这段时要特别盯工具和权限边界,别为了省事一把全开。

改配置

SubagentStart 1

这一段说完,最后还得写到配置里才算真的生效。

{
  "session_id": "abc123",
  "transcript_path": "/Users/.../.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
  "cwd": "/Users/...",
  "hook_event_name": "SubagentStart",
  "agent_id": "agent-abc123",
  "agent_type": "Explore"
}
改配置

SubagentStart 2

这一段说完,最后还得写到配置里才算真的生效。

{
  "hookSpecificOutput": {
    "hookEventName": "SubagentStart",
    "additionalContext": "Follow security guidelines for this task"
  }
}

SubagentStop

看到这里,就把"SubagentStop"当成一件真要上手的活来看。

这里还牵扯作用域,意思就是这条规则到底管当前项目、你个人,还是只管这一趟会话。

改配置

SubagentStop

这一段说完,最后还得写到配置里才算真的生效。

{
  "session_id": "abc123",
  "transcript_path": "~/.claude/projects/.../abc123.jsonl",
  "cwd": "/Users/...",
  "permission_mode": "default",
  "hook_event_name": "SubagentStop",
  "stop_hook_active": false,
  "agent_id": "def456",
  "agent_type": "Explore",
  "agent_transcript_path": "~/.claude/projects/.../abc123/subagents/agent-def456.jsonl",
  "last_assistant_message": "Analysis complete. Found 3 potential issues..."
}

照着做一遍

如果你不想来回翻,就先照这几步顺着做。

每做完一步就看一下结果,再决定要不要继续往下。

改配置

第 1 步:How a hook resolves 1

这一段说完,最后还得写到配置里才算真的生效。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "if": "Bash(rm *)",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-rm.sh"
          }
        ]
      }
    ]
  }
}
终端里敲

第 2 步:How a hook resolves 2

这一段不是只让你理解意思,下面这条命令就是现在要跑的。

#!/bin/bash
# .claude/hooks/block-rm.sh
COMMAND=$(jq -r '.tool_input.command')

if echo "$COMMAND" | grep -q 'rm -rf'; then
  jq -n '{
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      permissionDecision: "deny",
      permissionDecisionReason: "Destructive command blocked by hook"
    }
  }'
else
  exit 0  # allow the command
fi
改配置

第 3 步:How a hook resolves 3

这一段说完,最后还得写到配置里才算真的生效。

{ "tool_name": "Bash", "tool_input": { "command": "rm -rf /tmp/build" }, ... }
改配置

第 4 步:Matcher patterns 1

想把这条规矩固定住,就把下面这块老老实实写进去。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "/path/to/lint-check.sh"
          }
        ]
      }
    ]
  }
}

一眼看懂这一页

先把这页到底在讲什么看明白,再去碰具体命令和配置,最不容易绕晕。

Hooks reference
   |
   v
这是 Reference 里的一摊要紧活
   |
   v
先弄懂,再下手

文末提醒

这站会按官方 docs 的导航和内容变化继续重生成,原站加页、删页、改页时,这里会跟着更新。

人话解释会尽量顺着原页往下讲,但命令、参数名、配置名这些硬东西还是保留原样,免得你抄过去跑不起来。