mcpField
H
hook-developer

hook-developer

github.com/parcadei开发3.9k506 次安装39 周前更新
代码仓库

关于 hook-developer

供开发者创建和排查 Claude Code 钩子的参考技能,涵盖事件结构、阻止行为、注册、钩子类型、测试及常见实现模式。

主要功能

  1. 01钩子事件的输入与输出结构
  2. 02阻止操作和注入上下文的行为
  3. 03钩子注册与匹配器模式
  4. 04命令式和提示词式钩子
  5. 05测试与调试指南
  6. 06常见钩子实现模式

适用场景

  1. 01按正确的事件结构编写钩子
  2. 02判断钩子能否阻止操作
  3. 03在 `settings.json` 配置事件匹配器
  4. 04排查钩子输出或注册问题

MCP 相关

说明如何在 Claude Code 钩子中识别和匹配 MCP 工具,包括 MCP 服务工具名称的匹配模式。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/parcadei/continuous-claude-v3 --skill hook-developer

安装后在 Claude Code 中输入/hook-developer确认已加载。

详细介绍

钩子开发者

Claude Code 钩子开发的完整参考。使用本文档编写具有正确输入/输出架构的钩子。

适用场景

  • 创建新钩子
  • 调试钩子输入/输出格式
  • 了解可用字段
  • 在 settings.json 中设置钩子注册
  • 了解钩子可以阻止什么,或注入什么上下文

快速参考

钩子触发时机能否阻止?主要用途
PreToolUse工具执行前是阻止/修改工具调用
PostToolUse工具完成后部分可以对工具结果作出响应
UserPromptSubmit用户发送提示词时是验证/注入上下文
PermissionRequest显示权限对话框时是自动批准/拒绝
SessionStart会话开始时否加载上下文、设置环境变量
SessionEnd会话结束时否清理/保存状态
StopAgent 完成时是强制继续
SubagentStart子 Agent 启动时否模式协调
SubagentStop子 Agent 完成时是强制继续
PreCompact压缩前否保存状态
Notification发送通知时否自定义提醒

钩子类型选项: type: "command"(bash)或 type: "prompt"(LLM 评估)


钩子输入/输出架构

PreToolUse

用途: 在工具执行前阻止或修改其执行。

输入:

{
  "session_id": "string",
  "transcript_path": "string",
  "cwd": "string",
  "permission_mode": "default|plan|acceptEdits|bypassPermissions",
  "hook_event_name": "PreToolUse",
  "tool_name": "string",
  "tool_input": {
    "file_path": "string",
    "command": "string"
  },
  "tool_use_id": "string"
}

输出(JSON):

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow|deny|ask",
    "permissionDecisionReason": "string",
    "updatedInput": {}
  },
  "continue": true,
  "stopReason": "string",
  "systemMessage": "string",
  "suppressOutput": true
}

退出码 2: 阻止工具执行,并将 stderr 显示给 Claude。

常见匹配器: Bash、Edit|Write、Read、Task、mcp__.*


PostToolUse

用途: 对工具执行结果作出响应,并向 Claude 提供反馈。

输入:

{
  "session_id": "string",
  "transcript_path": "string",
  "cwd": "string",
  "permission_mode": "string",
  "hook_event_name": "PostToolUse",
  "tool_name": "string",
  "tool_input": {},
  "tool_response": {
    "filePath": "string",
    "success": true,
    "output": "string",
    "exitCode": 0
  },
  "tool_use_id": "string"
}

重要: 响应字段是 tool_response,而非 tool_result。

输出(JSON):

{
  "decision": "block",
  "reason": "string",
  "hookSpecificOutput": {
    "hookEventName": "PostToolUse",
    "additionalContext": "string"
  },
  "continue": true,
  "stopReason": "string",
  "suppressOutput": true
}

阻止: "decision": "block" 配合 "reason" 会提示 Claude 处理该问题。

常见匹配器: Edit|Write、Bash


UserPromptSubmit

用途: 验证用户提示词,并在 Claude 处理之前注入上下文。

输入:

{
  "session_id": "string",
  "transcript_path": "string",
  "cwd": "string",
  "permission_mode": "string",
  "hook_event_name": "UserPromptSubmit",
  "prompt": "string"
}

输出(纯文本):

Any stdout text is added to context for Claude.

输出(JSON):

{
  "decision": "block",
  "reason": "string",
  "hookSpecificOutput": {
    "hookEventName": "UserPromptSubmit",
    "additionalContext": "string"
  }
}

阻止: "decision": "block" 会清除提示词,并且只向用户显示 "reason"(Claude 看不到)。

退出码 2: 阻止提示词,并且只向用户显示 stderr。


PermissionRequest

用途: 自动处理权限对话框的决定。

输入:

{
  "session_id": "string",
  "transcript_path": "string",
  "cwd": "string",
  "permission_mode": "string",
  "hook_event_name": "PermissionRequest",
  "tool_name": "string",
  "tool_input": {}
}

输出:

{
  "hookSpecificOutput": {
    "hookEventName": "PermissionRequest",
    "decision": {
      "behavior": "allow|deny",
      "updatedInput": {},
      "message": "string",
      "interrupt": false
    }
  }
}

SessionStart

用途: 初始化会话、加载上下文、设置环境变量。

输入:

{
  "session_id": "string",
  "transcript_path": "string",
  "cwd": "string",
  "permission_mode": "string",
  "hook_event_name": "SessionStart",
  "source": "startup|resume|clear|compact"
}

环境变量: CLAUDE_ENV_FILE——写入 export VAR=value 以持久化环境变量。

输出(纯文本或 JSON):

{
  "hookSpecificOutput": {
    "hookEventName": "SessionStart",
    "additionalContext": "string"
  },
  "suppressOutput": true
}

标准输出中的纯文本会作为上下文添加。


SessionEnd

用途: 清理、保存状态、记录会话。

输入:

{
  "session_id": "string",
  "transcript_path": "string",
  "cwd": "string",
  "permission_mode": "string",
  "hook_event_name": "SessionEnd",
  "reason": "clear|logout|prompt_input_exit|other"
}

输出: 无法影响会话(会话已经结束)。仅用于清理。


Stop

用途: 控制 Claude 何时停止,并强制其继续。

输入:

{
  "session_id": "string",
  "transcript_path": "string",
  "cwd": "string",
  "permission_mode": "string",
  "hook_event_name": "Stop",
  "stop_hook_active": false
}

重要: 检查 stop_hook_active: true,以防止无限循环!

输出:

{
  "decision": "block",
  "reason": "string"
}

阻止: "decision": "block" 会强制 Claude 继续,并将 "reason" 作为提示词。


SubagentStart

用途: 子 Agent(Task 工具)启动时运行。

输入:

{
  "session_id": "string",
  "transcript_path": "string",
  "cwd": "string",
  "permission_mode": "string",
  "hook_event_name": "SubagentStart",
  "agent_id": "string"
}

输出: 仅注入上下文(无法阻止)。


SubagentStop

用途: 控制子 Agent(Task 工具)何时停止。

输入:

{
  "session_id": "string",
  "transcript_path": "string",
  "cwd": "string",
  "permission_mode": "string",
  "hook_event_name": "SubagentStop",
  "stop_hook_active": false
}

输出: 与 Stop 相同。


PreCompact

用途: 在上下文压缩前保存状态。

输入:

{
  "session_id": "string",
  "transcript_path": "string",
  "cwd": "string",
  "permission_mode": "string",
  "hook_event_name": "PreCompact",
  "trigger": "manual|auto",
  "custom_instructions": "string"
}

匹配器: manual、auto

输出:

{
  "continue": true,
  "systemMessage": "string"
}

Notification

用途: 自定义通知处理。

输入:

{
  "session_id": "string",
  "transcript_path": "string",
  "cwd": "string",
  "permission_mode": "string",
  "hook_event_name": "Notification",
  "message": "string",
  "notification_type": "permission_prompt|idle_prompt|auth_success|elicitation_dialog"
}

匹配器: permission_prompt、idle_prompt、auth_success、elicitation_dialog、*

输出:

{
  "continue": true,
  "suppressOutput": true,
  "systemMessage": "string"
}

在 settings.json 中注册

标准结构
{
  "hooks": {
    "EventName": [
      {
        "matcher": "ToolPattern",
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/my-hook.sh",
            "timeout": 60
          }
        ]
      }
    ]
  }
}
匹配模式
模式匹配内容
Bash仅 Bash 工具
Edit|WriteEdit 或 Write
Read.*正则表达式:Read*
mcp__.*__write.*MCP 写入工具
*所有工具

区分大小写: Bash ≠ bash

需要匹配器的事件
  • PreToolUse - 是(必需)
  • PostToolUse - 是(必需)
  • PermissionRequest - 是(必需)
  • Notification - 是(可选)
  • SessionStart - 是(startup|resume|clear|compact)
  • PreCompact - 是(manual|auto)
不需要匹配器的事件
{
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [{ "type": "command", "command": "/path/to/hook.sh" }]
      }
    ]
  }
}

钩子类型

命令钩子(类型:"command")

默认类型。执行 bash 命令或脚本。

{
  "type": "command",
  "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/my-hook.sh",
  "timeout": 60
}
基于提示词的钩子(类型:"prompt")

使用大模型(Haiku)进行上下文感知决策。最适用于 Stop/SubagentStop。

{
  "type": "prompt",
  "prompt": "Evaluate if Claude should stop. Context: $ARGUMENTS. Check if all tasks are complete.",
  "timeout": 30
}

响应架构:

{
  "decision": "approve" | "block",
  "reason": "Explanation",
  "continue": false,
  "stopReason": "Message to user",
  "systemMessage": "Warning"
}

MCP 工具命名

MCP 工具使用模式 mcp__<server>__<tool>:

模式匹配内容
mcp__memory__.*所有 memory 服务工具
mcp__.*__write.*所有 MCP 写入工具
mcp__github__.*所有 GitHub 工具

环境变量

所有钩子均可使用
变量说明
CLAUDE_PROJECT_DIR项目根目录的绝对路径
CLAUDE_CODE_REMOTE远程/Web 环境为 "true",本地 CLI 环境为空
仅限 SessionStart
变量说明
CLAUDE_ENV_FILE用于写入 export VAR=value 行的路径
仅限插件钩子
变量说明
CLAUDE_PLUGIN_ROOT插件目录的绝对路径

退出代码

退出代码行为stdoutstderr
0成功已处理 JSON忽略
2阻止性错误忽略错误消息
其他非阻止性错误忽略详细模式
各钩子的退出代码 2
钩子效果
PreToolUse阻止工具,并将 stderr 传给 Claude
PostToolUse将 stderr 传给 Claude(工具已运行)
UserPromptSubmit阻止提示词,并仅将 stderr 传给用户
Stop阻止停止,并将 stderr 传给 Claude

Shell 包装器模式

#!/bin/bash
set -e
cd "$CLAUDE_PROJECT_DIR/.claude/hooks"
cat | npx tsx src/my-hook.ts

或用于打包版本:

#!/bin/bash
set -e
cd "$HOME/.claude/hooks"
cat | node dist/my-hook.mjs

TypeScript 处理器模式

import { readFileSync } from 'fs';

interface HookInput {
  session_id: string;
  hook_event_name: string;
  tool_name?: string;
  tool_input?: Record<string, unknown>;
  tool_response?: Record<string, unknown>;
  // ... other fields per hook type
}

function readStdin(): string {
  return readFileSync(0, 'utf-8');
}

async function main() {
  const input: HookInput = JSON.parse(readStdin());

  // Process input

  const output = {
    decision: 'block',  // or undefined to allow
    reason: 'Why blocking'
  };

  console.log(JSON.stringify(output));
}

main().catch(console.error);

测试钩子

手动测试命令
# PostToolUse (Write)
echo '{"tool_name":"Write","tool_input":{"file_path":"test.md"},"tool_response":{"success":true},"session_id":"test"}' | \
  .claude/hooks/my-hook.sh

# PreToolUse (Bash)
echo '{"tool_name":"Bash","tool_input":{"command":"ls"},"session_id":"test"}' | \
  .claude/hooks/my-hook.sh

# SessionStart
echo '{"hook_event_name":"SessionStart","source":"startup","session_id":"test"}' | \
  .claude/hooks/session-start.sh

# SessionEnd
echo '{"hook_event_name":"SessionEnd","reason":"clear","session_id":"test"}' | \
  .claude/hooks/session-end.sh

# UserPromptSubmit
echo '{"prompt":"test prompt","session_id":"test"}' | \
  .claude/hooks/prompt-submit.sh
修改 TypeScript 后重新构建
cd .claude/hooks
npx esbuild src/my-hook.ts \
  --bundle --platform=node --format=esm \
  --outfile=dist/my-hook.mjs

常见模式

阻止危险文件(PreToolUse)
#!/usr/bin/env python3
import json, sys

data = json.load(sys.stdin)
path = data.get('tool_input', {}).get('file_path', '')

BLOCKED = ['.env', 'secrets.json', '.git/']
if any(b in path for b in BLOCKED):
    print(json.dumps({
        "hookSpecificOutput": {
            "hookEventName": "PreToolUse",
            "permissionDecision": "deny",
            "permissionDecisionReason": f"Blocked: {path} is protected"
        }
    }))
else:
    print('{}')
自动格式化文件(PostToolUse)
#!/bin/bash
INPUT=$(cat)
FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

if [[ "$FILE" == *.ts ]] || [[ "$FILE" == *.tsx ]]; then
  npx prettier --write "$FILE" 2>/dev/null
fi

echo '{}'
注入 Git 上下文(UserPromptSubmit)
#!/bin/bash
echo "Git status:"
git status --short 2>/dev/null || echo "(not a git repo)"
echo ""
echo "Recent commits:"
git log --oneline -5 2>/dev/null || echo "(no commits)"
强制验证测试(Stop)
#!/usr/bin/env python3
import json, sys, subprocess

data = json.load(sys.stdin)

# Prevent infinite loops
if data.get('stop_hook_active'):
    print('{}')
    sys.exit(0)

# Check if tests pass
result = subprocess.run(['npm', 'test'], capture_output=True)
if result.returncode != 0:
    print(json.dumps({
        "decision": "block",
        "reason": "Tests are failing. Please fix before stopping."
    }))
else:
    print('{}')

调试检查清单

  • 是否已在 settings.json 中注册钩子?
  • Shell 脚本是否具有 +x 权限?
  • 修改 TS 后是否重新构建了打包文件?
  • 是否使用了 tool_response 而非 tool_result?
  • 输出是否为有效 JSON(或纯文本)?
  • 是否在 Stop 钩子中检查了 stop_hook_active?
  • 路径是否使用了 $CLAUDE_PROJECT_DIR?

以往会话的经验

  1. 字段名称很重要 - 使用 tool_response 而非 tool_result
  2. 输出格式 - 阻止时使用 decision: "block" + reason
  3. 退出代码 2 - stderr 会传给 Claude/用户,stdout 会被忽略
  4. 重新构建打包文件 - TypeScript 源文件的修改不会自动生效
  5. 手动测试 - 使用 echo '{}' | ./hook.sh 测试后再依赖它
  6. 先检查输出 - 修改代码前先运行 ls .claude/cache/
  7. 分离式 spawn 会隐藏错误 - 添加日志以便调试

另请参阅

  • /debug-hooks - 系统化调试工作流
  • .claude/rules/hooks.md - 钩子开发规则

评论

支持 Markdown