mcpField
H
hook-authoring

hook-authoring

github.com/atholaAgent 工作流343109 次安装1 天前更新
代码仓库

关于 hook-authoring

为 Claude Code 和 Claude Agent SDK 编写生命周期钩子的指南型技能,涵盖声明式 JSON 配置与 Python 实现。内容还涉及事件处理、安全和性能注意事项。

主要功能

  1. 01Claude Code JSON 钩子配置
  2. 02使用 Claude Agent SDK 编写 Python 钩子
  3. 03生命周期事件处理
  4. 04输入验证与输出处理
  5. 05安全与性能指导

适用场景

  1. 01执行前验证工具输入
  2. 02记录或转换工具输出
  3. 03注入上下文或过滤提交的提示词
  4. 04上下文压缩前保存状态

安装

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

$npx skills add https://github.com/athola/claude-night-market --skill hook-authoring

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

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

modules/
hook-types.md
observability-warnings.md
performance-guidelines.md
scope-selection.md
sdk-callbacks.md
testing-hooks.md
scripts/
README.md

详细介绍

不应使用的情况

  • 审核已存在的钩子(使用 abstract:hooks-eval)
  • 选择钩子应放在哪里(使用 abstract:hook-scope-guide)
  • 编写技能而非钩子(使用 abstract:skill-authoring)

钩子编写指南

概述

钩子是事件拦截器,可在 Agent 生命周期的特定节点执行自定义逻辑,从而扩展 Claude Code 和 Claude Agent SDK 的行为。它们支持在使用工具前进行验证、在操作后记录日志、注入上下文、自动化工作流以及执行安全策略。

本技能将教你如何为声明式 JSON(Claude Code)和编程式 Python(Claude Agent SDK)用例编写有效、安全且高性能的钩子。

主要功能
  • PreToolUse:在执行前验证、筛选或转换工具输入;注入上下文(2.1.9+)
  • PostToolUse:在执行后记录、分析或修改工具输出
  • UserPromptSubmit:在处理用户消息前注入上下文或筛选消息
  • Stop/SubagentStop:清理、最终报告或结果汇总
  • TeammateIdle/TaskCompleted:多 Agent 协调与编排(2.1.33+)
  • PreCompact:在上下文窗口压缩前保留状态

2.1.9 新增:PreToolUse 钩子现在可以返回 additionalContext,以便在工具执行前注入信息。这支持缓存提示、安全警告或相关上下文注入等模式。

快速开始

你的第一个钩子(JSON - Claude Code)

在 .claude/settings.json 中创建一个简单的日志钩子:

{
  "PostToolUse": [
    {
      "matcher": "Bash",
      "hooks": [{
        "type": "command",
        "command": "echo \"$(date): Executed $(jq -r '.tool_name')\" >> ~/.claude/audit.log"
      }]
    }
  ]
}

注意:使用字符串匹配器("Bash"),而非对象匹配器({"toolName": "Bash"})。

**验证:**使用 --help 标志运行命令,以验证其是否可用。

此钩子会记录每次 Bash 命令执行及其时间戳。

你的第一个钩子(Python - Claude Agent SDK)

使用 SDK 创建一个验证钩子:

from claude_agent_sdk import AgentHooks


class ValidationHooks(AgentHooks):
    async def on_pre_tool_use(self, tool_name: str, tool_input: dict) -> dict | None:
        """Validate tool inputs before execution."""
        if tool_name == "Bash":
            command = tool_input.get("command", "")
            if "rm -rf /" in command:
                raise ValueError("Dangerous command blocked by hook")

        # Return None to proceed unchanged, or modified dict to transform
        return None

**验证:**使用 --help 标志运行命令,以验证其是否可用。

钩子事件类型

所有受支持钩子事件的快速参考:

事件触发点参数常见用途
PreToolUse工具执行前tool_name、tool_input验证、筛选、输入转换
PostToolUse工具执行后tool_name、tool_input、tool_output日志记录、指标、输出转换
UserPromptSubmit用户发送消息时message上下文注入、内容筛选
PermissionRequest显示权限对话框时tool_name、tool_input使用自定义逻辑自动批准或拒绝
NotificationClaude Code 发送通知时message自定义通知处理
StopAgent 完成时reason、result最终清理、摘要报告
SubagentStop子 Agent 完成时subagent_id、result结果处理、汇总
TeammateIdle队友 Agent 进入空闲状态时agent_id、session_id任务分配、负载均衡(2.1.33+)
TaskCompleted任务执行完成时task_id、result协调、链接、报告(2.1.33+)
PreCompact上下文压缩前context_size状态保留、检查点
SessionStart会话开始或恢复时session_id、source、agent_type初始化、加载上下文
SessionEnd会话终止时session_id清理、最终日志记录
WorktreeCreate创建 Agent 工作树时worktree_path、session_id自定义 VCS 设置、软链接 .venv、预填充缓存(2.1.50+)
WorktreeRemove移除 Agent 工作树时worktree_path、session_id清理临时文件、拆除工作树范围内的资源(2.1.50+)
SessionStart 输入架构(Claude Code 2.1.2+)

SessionStart 钩子通过 stdin 接收 JSON 输入,其中包含以下字段:

{
  "session_id": "abc123",
  "source": "startup",
  "agent_type": "my-agent"
}

字段:source 的值为 "startup"、"resume"、"clear" 或 "compact"。使用 --agent 标志时会填充 agent_type。

agent_type 字段:使用 --agent my-agent 启动 Claude Code 时,此字段包含 Agent 名称,可用于 Agent 专属初始化:

# Python example: Agent-aware SessionStart hook
input_data = json.loads(sys.stdin.read())
agent_type = input_data.get("agent_type", "")

if agent_type in ["code-reviewer", "quick-query"]:
    # Skip heavy context injection for lightweight agents
    print(json.dumps({"hookSpecificOutput": {"additionalContext": "Minimal context"}}))
else:
    # Full initialization for implementation agents
    print(json.dumps({"hookSpecificOutput": {"additionalContext": full_context}}))
# Bash example: Agent-aware SessionStart hook
HOOK_INPUT=$(cat)
AGENT_TYPE=$(echo "$HOOK_INPUT" | jq -r '.agent_type // empty')

case "$AGENT_TYPE" in
    code-reviewer|quick-query)
        echo '{"hookSpecificOutput": {"additionalContext": "Minimal context"}}'
        ;;
    *)
        echo '{"hookSpecificOutput": {"additionalContext": "Full context"}}'
        ;;
esac

Frontmatter 中的钩子(Claude Code 2.1.0+)

**2.1.0 新增:**可直接在技能、命令或 Agent 的 frontmatter 中定义钩子。这些钩子的作用范围限定为相应组件的生命周期。

技能/命令/Agent Frontmatter 钩子
---
name: validated-skill
description: Skill with lifecycle hooks
hooks:
  PreToolUse:
    - matcher: "Bash"
      command: "./validate-command.sh"
      once: true  # NEW: Run only once per session
    - matcher: "Write|Edit"
      command: "./pre-edit-check.sh"
  PostToolUse:
    - matcher: "Write|Edit"
      command: "./format-on-save.sh"
  Stop:
    - command: "./cleanup-and-report.sh"
---
once: true 配置

**2.1.0 新增:**使用 once: true 可让钩子在每个会话中仅执行一次,适用于:

  • 一次性设置/初始化
  • 不应重复执行的资源分配
  • 会话级配置
hooks:
  PreToolUse:
    - matcher: "Bash"
      command: "./setup-environment.sh"
      once: true  # Runs only on first Bash call
  SessionStart:
    - command: "./initialize-session.sh"
      once: true  # Runs only once at session start
Frontmatter 钩子与设置钩子
方面Frontmatter 钩子设置钩子
作用范围组件生命周期全局/项目
位置技能/Agent/命令中settings.json
持续时间仅在组件运行时生效始终生效
使用场景组件专属验证跨领域问题
PreToolUse updatedInput(2.1.0 修复)

PreToolUse 钩子现在可以在返回 ask 权限决策时返回 updatedInput,让钩子在请求用户同意的同时充当中间件:

{
  "decision": "ask",
  "updatedInput": {
    "command": "modified-command --safe-flag"
  }
}

Claude Code 与 SDK

JSON 钩子(Claude Code)

在 .claude/settings.json、项目的 .claude/settings.json 或插件的 hooks/hooks.json 中进行声明式配置:

{
  "PreToolUse": [
    {
      "matcher": "Edit",
      "hooks": [{
        "type": "command",
        "command": "echo 'WARNING: Editing production file' >&2"
      }]
    }
  ]
}

**重要:**使用字符串匹配器(正则表达式模式),而非对象匹配器。对象格式 {"toolName": "Edit"} 已弃用。

匹配器模式:

  • "Edit" - 匹配单个工具
  • "Read|Write|Edit" - 匹配多个工具(正则表达式 OR)
  • ".*" - 匹配所有工具

**验证:**使用 --help 标志运行命令,以确认其可用性。

**优点:**简单,无需编写代码,易于进行版本控制 **缺点:**逻辑能力有限,仅支持 shell 命令

HTTP 钩子(Claude Code 2.1.63+)

**2.1.63 新增:**钩子可以向 URL 发送 JSON POST 请求并接收 JSON 响应,而不是运行 shell 命令。使用 "type": "http" 并指定 "url" 字段:

{
  "PreToolUse": [
    {
      "matcher": "Bash",
      "hooks": [{
        "type": "http",
        "url": "https://my-service.example.com/hooks/validate-bash"
      }]
    }
  ]
}

钩子会将标准钩子输入作为 JSON 发送 POST 请求,并要求响应正文为标准钩子响应 JSON。

适合使用 HTTP 钩子而非命令钩子的场景:

  • 限制 shell 执行的企业环境
  • 通过 Web 服务在团队间共享的集中式钩子逻辑
  • 无法访问本地脚本的沙箱或容器化环境
  • 与外部验证/日志服务集成

**优点:**无需本地脚本,逻辑集中,可在沙箱环境中运行 **缺点:**网络延迟,需要运行 HTTP 服务,依赖外部服务

Python SDK 钩子

使用 AgentHooks 基类实现编程式回调:

from claude_agent_sdk import AgentHooks


class MyHooks(AgentHooks):
    async def on_pre_tool_use(self, tool_name: str, tool_input: dict) -> dict | None:
        # Complex validation logic
        if self._is_dangerous(tool_input):
            raise ValueError("Operation blocked")
        return None  # or return modified input

**验证:**使用 --help 标志运行命令,以确认其可用性。

**优点:**充分利用 Python 功能,支持复杂逻辑和状态管理 **缺点:**需要 Python,设置较复杂

Bash 权限匹配说明

环境变量包装(2.1.38+)

现在,权限规则能够正确匹配带有环境变量赋值前缀的命令。在 2.1.38 之前,NODE_ENV=production npm test 无法匹配 Bash(npm *) 规则。

# These now all match `Bash(npm *)`:
npm test
NODE_ENV=production npm test
FORCE_COLOR=1 CI=true npm test

编写用于检查 bash 命令的 PreToolUse 钩子时请注意:权限系统会在匹配时去除环境变量前缀,但钩子接收的完整命令字符串仍包含这些前缀。

Heredoc 分隔符安全(2.1.38+)

Claude Code 现在会验证 heredoc 分隔符,以防止命令注入。推荐使用的模式 <<'EOF'(单引号括起)仍是最安全的方式。在 heredoc 模式中始终使用单引号分隔符,以防止变量展开。

安全要点

关键安全规则
  1. 输入验证:处理前始终验证工具输入
  2. 不记录机密:切勿记录 API 密钥、令牌、密码或凭据
  3. 注意沙箱边界:遵守沙箱边界,不要越界。注意:在沙箱模式下,.claude/skills/ 为只读(2.1.38+)
  4. 安全失败默认值:发生错误时返回 None,而不是阻塞 Agent
  5. 速率限制:防止恶意或有缺陷的代码滥用钩子
  6. 防止注入:清理所有记录的内容,防止日志注入
示例:安全日志记录钩子
import re
from claude_agent_sdk import AgentHooks


class SecureLoggingHooks(AgentHooks):
    # Patterns that might contain secrets
    SECRET_PATTERNS = [
        r"api[_-]?key",
        r"password",
        r"token",
        r"secret",
        r"credential",
        r"auth",
    ]

    def _sanitize_output(self, text: str) -> str:
        """Remove potential secrets from log output."""
        for pattern in self.SECRET_PATTERNS:
            text = re.sub(
                rf'({pattern}["\s:=]+)([^\s,}}]+)',
                r"\1***REDACTED***",
                text,
                flags=re.IGNORECASE,
            )
        return text

    async def on_post_tool_use(
        self, tool_name: str, tool_input: dict, tool_output: str
    ) -> str | None:
        """Log tool use with sanitization."""
        safe_output = self._sanitize_output(tool_output)
        # Log safe_output...
        return None  # Don't modify output

**验证:**运行带有 --help 标志的命令,以确认其可用。

有关详细的安全指南,请参阅 modules/testing-hooks.md。

性能指南

性能最佳实践
  1. 非阻塞:正确使用 async/await,不要阻塞事件循环
  2. 超时处理:钩子超时时间为 10 分钟(在 2.1.3 中从 60s 延长)。对于大多数钩子,目标应为 < 30s;仅在 CI/CD 集成、复杂验证或调用外部 API 时使用延长后的时间
  3. 高效日志记录:批量写入,使用异步 I/O
  4. 内存管理:不要无限累积状态
  5. 快速失败:快速验证、尽早返回,避免开销大的操作
示例:高效钩子
import asyncio
from claude_agent_sdk import AgentHooks


class EfficientHooks(AgentHooks):
    def __init__(self):
        self._log_queue = asyncio.Queue()
        self._log_task = None

    async def on_pre_tool_use(self, tool_name: str, tool_input: dict) -> dict | None:
        # Quick validation only
        if not self._is_valid_input(tool_input):
            raise ValueError("Invalid input")
        return None

    async def on_post_tool_use(
        self, tool_name: str, tool_input: dict, tool_output: str
    ) -> str | None:
        # Queue log entry without blocking
        await self._log_queue.put({"tool": tool_name, "timestamp": time.time()})
        return None

    def _is_valid_input(self, tool_input: dict) -> bool:
        """Fast validation check."""
        # Simple checks only, < 10ms
        return len(str(tool_input)) < 1_000_000

**验证:**运行带有 --help 标志的命令,以确认其可用。

有关详细的优化技巧,请参阅 modules/performance-guidelines.md。

范围选择

根据受众和用途,为钩子选择合适的位置。

重要:自动加载行为

启用插件时,会自动加载 hooks/hooks.json。 不要在 plugin.json 中添加 "hooks": "./hooks/hooks.json"——这会导致重复加载错误。 只有在需要加载标准位置以外的其他钩子文件时,才使用 hooks 字段。

决策框架
**Verification:** Run the command with `--help` flag to verify availability.
Is this hook part of a plugin's core functionality?
├─ YES → Plugin hooks (hooks/hooks.json in plugin)
└─ NO ↓

Should all team members on this project have this hook?
├─ YES → Project hooks (.claude/settings.json)
└─ NO ↓

Should this hook apply to all my Claude sessions?
├─ YES → Global hooks (~/.claude/settings.json)
└─ NO → Reconsider if you need a hook at all

**验证:**运行带有 --help 标志的命令,以确认其可用。

范围比较
范围位置受众是否提交?示例用途
插件hooks/hooks.json插件用户是(随插件提交)在 YAML 插件中验证 YAML
项目.claude/settings.json团队成员是(在仓库中)阻止编辑生产配置
全局~/.claude/settings.json仅你自己从不个人审计日志记录

有关详细的范围决策指南,请参阅 modules/scope-selection.md。

常见模式

验证钩子

在执行前阻止危险操作:

async def on_pre_tool_use(self, tool_name: str, tool_input: dict) -> dict | None:
    if tool_name == "Bash":
        command = tool_input.get("command", "")

        # Block dangerous patterns
        if any(pattern in command for pattern in ["rm -rf /", ":(){ :|:& };:"]):
            raise ValueError(f"Dangerous command blocked: {command}")

        # Block production access
        if "production" in command and not self._has_approval():
            raise ValueError("Production access requires approval")

    return None

**验证:**运行带有 --help 标志的命令,以确认其可用。

日志记录钩子

审计所有工具操作:

async def on_post_tool_use(
    self, tool_name: str, tool_input: dict, tool_output: str
) -> str | None:
    await self._log_entry(
        {
            "timestamp": datetime.now().isoformat(),
            "tool": tool_name,
            "input_size": len(str(tool_input)),
            "output_size": len(tool_output),
            "success": True,
        }
    )
    return None

**验证:**运行带有 --help 标志的命令,以确认其可用。

上下文注入钩子

在用户提示词前添加相关上下文:

async def on_user_prompt_submit(self, message: str) -> str | None:
    # Inject project-specific context
    context = await self._load_project_context()
    enhanced_message = f"{context}\n\n{message}"
    return enhanced_message
PreToolUse 上下文注入(Claude Code 2.1.9+)

使用 additionalContext 在工具执行前注入上下文:

#!/usr/bin/env python3
"""PreToolUse hook that injects context before WebFetch."""

import json
import sys


def main():
    payload = json.load(sys.stdin)
    tool_name = payload.get("tool_name", "")

    if tool_name == "WebFetch":
        url = payload.get("tool_input", {}).get("url", "")
        # Check cache or knowledge base
        cached = lookup_knowledge_base(url)
        if cached:
            print(
                json.dumps(
                    {
                        "hookSpecificOutput": {
                            "hookEventName": "PreToolUse",
                            "additionalContext": f"Relevant cached info: {cached}",
                        }
                    }
                )
            )
    sys.exit(0)


if __name__ == "__main__":
    main()

此模式适用于:在 Web 请求前提供缓存提示、在高风险操作前发出安全警告,以及在文件操作前注入相关项目上下文。

测试钩子

单元测试
import pytest
from my_hooks import ValidationHooks


@pytest.mark.asyncio
async def test_dangerous_command_blocked():
    hooks = ValidationHooks()

    with pytest.raises(ValueError, match="Dangerous command"):
        await hooks.on_pre_tool_use("Bash", {"command": "rm -rf /"})


@pytest.mark.asyncio
async def test_safe_command_allowed():
    hooks = ValidationHooks()
    result = await hooks.on_pre_tool_use("Bash", {"command": "ls -la"})
    assert result is None  # Allows execution

**验证:**运行 pytest -v from 进行验证。

有关详细的测试策略,请参阅 modules/testing-hooks.md。

模块参考

有关特定主题的详细指南:

  • 钩子类型:modules/hook-types.md - 详细的事件签名和参数
  • SDK 回调:modules/sdk-callbacks.md - Python SDK 实现模式
  • 安全模式:modules/testing-hooks.md - 详细的安全指南
  • 性能指南:modules/performance-guidelines.md - 优化技巧
  • 作用域选择:modules/scope-selection.md - 选择插件/项目/全局作用域
  • 钩子测试:modules/testing-hooks.md - 测试策略和固件
  • 可观测性警告:modules/observability-warnings.md - 可复制粘贴的解决方案模式,适用于可通过二进制操作的漂移钩子

工具

  • hook_validator.py:验证钩子结构和语法(位于 plugins/abstract/scripts/hook_validator.py)

相关技能

  • hook-scope-guide:钩子放置决策框架(现有)
  • modular-skills:技能架构设计模式
  • skills-eval:质量评估和改进框架

后续步骤

  1. 根据复杂度需求选择钩子类型(JSON 或 SDK)
  2. 选择适当的作用域(插件/项目/全局)
  3. 遵循安全和性能最佳实践进行实现
  4. 使用单元测试和集成测试进行全面测试
  5. 部署前使用 hook_validator.py 验证

环境变量(Claude Code 2.1.2+)

FORCE_AUTOUPDATE_PLUGINS

即使主 Claude Code 自动更新程序已禁用,也强制自动更新插件。

适用场景:

  • 需要最新插件版本的 CI/CD 流水线
  • 用于测试插件更新的开发环境
  • 企业环境中受控的更新推出
# Enable forced plugin updates
export FORCE_AUTOUPDATE_PLUGINS=1
claude

# Or inline
FORCE_AUTOUPDATE_PLUGINS=1 claude --agent my-agent

注意:这只影响插件更新,不影响 Claude Code 核心更新。

参考资料

钩子退出代码

钩子通过退出代码向 Claude Code 传达决策:

退出代码含义stdoutstderr
0成功/允许作为系统上下文显示给 Claude忽略
2阻止/拒绝忽略作为说明显示给用户(2.1.39+ 修复)
其他错误忽略作为错误消息显示给用户
使用退出代码 2 阻止操作(2.1.39+)

使用退出代码 2 阻止操作并向用户显示消息:

#!/bin/bash
# Example: Block force pushes with user-facing message
command=$(echo "$1" | jq -r '.tool_input.command // empty')
if echo "$command" | grep -q 'push.*--force'; then
  echo "Force push blocked: use --force-with-lease instead" >&2
  exit 2
fi
exit 0

重要:在 Claude Code 2.1.39 之前,退出代码 2 的 stderr 会被静默丢弃(#10964)。用户看到的会是通用的“钩子错误”,而不是自定义消息。此问题现已修复:stderr 会正确显示给用户。

插件钩子:在 2.1.39 之前,插件安装的钩子使用了单独的代码路径,同样无法显示退出代码 2 的 stderr(#10412)。现在插件钩子和项目钩子都能正常工作。

故障排查

常见问题

钩子未触发 确认钩子模式与事件匹配。检查钩子日志中是否有错误

语法错误 部署前验证 JSON/Python 语法

权限被拒绝 检查钩子文件的权限和所有权

未显示钩子阻止消息(2.1.39 之前) 如果使用退出代码 2 阻止操作并向用户显示消息,但消息未显示,请升级到 Claude Code 2.1.39+。在较旧版本中,可使用退出代码 0 并通过 stdout 输出消息作为替代方案。

退出条件

  • 编写的钩子文件位于有效的作用域位置(hooks/hooks.json、 .claude/settings.json 或 ~/.claude/settings.json),且 JSON 或 Python 语法正确。
  • 钩子会在目标事件发生时触发:调用匹配的工具时,钩子命令或回调会正常运行。
  • 钩子不会记录机密:日志输出路径中不会出现与 api[_-]?key、password、 token、secret 或 credential 匹配的字段名。
  • 阻塞型钩子以代码 2 退出,并将面向用户的说明写入 stderr(而非 stdout)。
  • 如果 abstract:validate-hook 可用,它会对编写的钩子文件返回退出码 0。

评论

支持 Markdown