Agent工坊

【Agent工坊】Claude Code 自定义 Hook 实战:用 pre/post 钩子打造自动质量闸门

如果你的AI Agent每天产出几十篇文章、上百次代码提交,但你无法确认每一次输出的质量——你其实不是在创业,你是在制造技术债。Claude Code 的 Hooks 系统可以让你在Agent的每一次行动前后,自动插入质量检查——零人工干预。

问题:Agent流水线的"黑箱"困境

使用 Claude Code(或 Hermes delegate_task)做内容生产时,一个典型的痛点:

帮我写一篇关于 MCP 协议的文章
Claude Code好的开始输出
5分钟后...
Claude Code已完成文章在此

你打开一看
- 字数只有 600 你需要 2000+
- 包含一个过时的 API 端点
- 没有配图
- 代码示例缺少 import 语句

你不得不再跑一轮修正。更糟的是——如果你用的是 cron 定时任务,你甚至不知道出了问题,直到3天后打开公众号后台才发现。

Claude Code 的 Hooks 系统就是为这类问题设计的。它让你在 Agent 执行操作的特定生命周期节点上,自动运行验证脚本。

Claude Code Hooks 基础概念

Claude Code 支持 9 种 Hook 事件:

事件 触发时机 典型用途
PreToolUse 工具调用前 权限检查、参数校验
PostToolUse 工具调用后 输出验证、日志记录
Notification 权限请求通知 自定义通知渠道
UserPromptSubmit 用户提交提示词 提示词注入安全
Stop Agent 停止响应 最终输出检查
SubagentStop 子Agent停止 子任务结果验证
PreCompact 上下文压缩前 关键信息保留检查
SessionStart 会话开始 环境初始化
SessionEnd 会话结束 清理、生成报告

今天聚焦最实用的三个PostToolUseStopPreToolUse——它们能覆盖 90% 的质量管控场景。

实战一:Stop Hook — 最终输出质量闸门

这是最常用的 Hook。当 Agent 完成响应、准备把结果返回给你之前触发。在这个节点,你可以在结果"交付"前做最后一道检查。

配置位置

在项目根目录创建 .claude/settings.json

{
  "hooks": {
    "Stop": [
      {
        "matcher": "",
        "command": "python3 .claude/hooks/quality_gate.py"
      }
    ]
  }
}

质量闸门脚本

.claude/hooks/quality_gate.py

#!/usr/bin/env python3
"""
Claude Code Stop Hook: 文章质量闸门
检查最终输出的内容是否满足最低质量标准
"""
import sys
import json
import re

def check_word_count(text: str, min_chinese_chars: int = 800) -> bool:
    """检查中文汉字数量"""
    chinese_chars = len(re.findall(r'[\u4e00-\u9fff]', text))
    return chinese_chars >= min_chinese_chars

def check_structure(text: str) -> bool:
    """检查是否包含必要章节"""
    required_sections = ['##', '###']  # 至少有二级标题
    return any(section in text for section in required_sections)

def check_code_blocks(text: str) -> bool:
    """如果提到代码,必须有代码块"""
    mentions_code = any(kw in text for kw in ['代码', 'code', '配置', 'config'])
    has_code_block = '```' in text
    if mentions_code and not has_code_block:
        return False
    return True

def check_forbidden_patterns(text: str) -> list:
    """检查禁止出现的模式"""
    forbidden = [
        (r'本章字数', '不应出现"本章字数"标注'),
        (r'\[待补充\]', '有未完成的内容'),
        (r'TODO', '有TODO标记'),
    ]
    issues = []
    for pattern, desc in forbidden:
        if re.search(pattern, text):
            issues.append(desc)
    return issues

def main():
    # Claude Code 通过 stdin 传递上下文(JSON)
    input_data = json.loads(sys.stdin.read())

    # 获取 Agent 的最终输出文本
    text = input_data.get('stop_hook_active', False) and \
           input_data.get('transcript', '')

    if not text:
        print("⚠️ 无法获取输出文本,跳过检查", file=sys.stderr)
        sys.exit(0)  # 非阻塞退出

    issues = []

    # 检查1:字数
    if not check_word_count(text):
        issues.append(f"❌ 汉字不足800字(当前{len(re.findall(r'[\u4e00-\u9fff]', text))}字)")

    # 检查2:结构
    if not check_structure(text):
        issues.append("❌ 缺少必要的章节划分(需要 ## 二级标题)")

    # 检查3:代码块
    if not check_code_blocks(text):
        issues.append("❌ 文章提到代码但没有代码块")

    # 检查4:禁止模式
    issues.extend(f"❌ {i}" for i in check_forbidden_patterns(text))

    if issues:
        print("\n".join([
            "🚫 质量闸门拦截:以下问题必须修复后才能交付",
            "=" * 50,
            *issues,
            "=" * 50,
            "请修复以上问题后重新生成",
        ]), file=sys.stderr)
        sys.exit(1)  # 阻塞输出,Agent 会收到失败信号

    print(f"✅ 质量检查通过:{len(re.findall(r'[\u4e00-\u9fff]', text))}字,结构完整", file=sys.stderr)
    sys.exit(0)

if __name__ == '__main__':
    main()

关键机制sys.exit(1) 会通知 Claude Code "输出不合格",Agent 会收到反馈并重新生成——无需人工介入

实战二:PostToolUse Hook — 每次文件写入后立即验证

Stop Hook 是最后一道防线,但如果能在问题产生的源头拦截,效率更高。PostToolUse 在每次工具调用(如 Write 文件、执行命令)后触发。

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write",
        "command": "python3 .claude/hooks/validate_file.py"
      }
    ]
  }
}

.claude/hooks/validate_file.py

#!/usr/bin/env python3
"""
PostToolUse Hook: 每次文件写入后校验
检查文件语法、格式、完整性
"""
import sys
import json
import os
import subprocess

def validate_markdown(filepath: str) -> list:
    """校验 Markdown 文件"""
    issues = []

    if not os.path.exists(filepath):
        return [f"文件不存在: {filepath}"]

    with open(filepath, 'r', encoding='utf-8') as f:
        content = f.read()

    # 检查是否为空文件
    if len(content.strip()) < 100:
        issues.append("文件内容过短(<100字符),可能写入失败")

    # 检查是否有 front matter 格式错误
    if content.startswith('---'):
        parts = content.split('---')
        if len(parts) < 3:
            issues.append("YAML front matter 格式不完整")

    return issues

def validate_python(filepath: str) -> list:
    """校验 Python 文件语法"""
    try:
        subprocess.run(
            ['python3', '-m', 'py_compile', filepath],
            capture_output=True, check=True, timeout=10
        )
        return []
    except subprocess.CalledProcessError as e:
        return [f"Python 语法错误: {e.stderr.decode()[-200:]}"]
    except Exception as e:
        return [f"校验异常: {str(e)}"]

def main():
    input_data = json.loads(sys.stdin.read())
    tool_name = input_data.get('tool_name', '')
    tool_input = input_data.get('tool_input', {})

    # 只处理文件写入操作
    if tool_name not in ['Write', 'Edit']:
        sys.exit(0)

    filepath = tool_input.get('file_path', '')
    if not filepath:
        sys.exit(0)

    # 根据文件类型选择校验器
    ext = os.path.splitext(filepath)[1]
    validators = {
        '.md': validate_markdown,
        '.py': validate_python,
    }

    validator = validators.get(ext)
    if not validator:
        sys.exit(0)  # 不支持的文件类型,放行

    issues = validator(filepath)

    if issues:
        print(f"🚫 文件 {filepath} 校验失败:", file=sys.stderr)
        for issue in issues:
            print(f"  - {issue}", file=sys.stderr)
        sys.exit(1)

    print(f"✅ {filepath} 校验通过", file=sys.stderr)
    sys.exit(0)

if __name__ == '__main__':
    main()

实战三:PreToolUse Hook — 操作前权限管控

这是安全层面的闸门。每当 Agent 要执行某个工具时,可以先检查是否在白名单内。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "command": "python3 .claude/hooks/bash_guard.py"
      }
    ]
  }
}

.claude/hooks/bash_guard.py

#!/usr/bin/env python3
"""PreToolUse Hook: Bash 命令白名单"""
import sys
import json

# 允许的命令前缀(白名单)
ALLOWED_COMMANDS = [
    'ls', 'cat', 'head', 'tail', 'echo', 'grep', 'find',
    'python3', 'node', 'npm', 'git status', 'git diff',
    'git log', 'wc', 'mkdir', 'touch', 'cp', 'mv',
]

# 永远禁止的命令
BLOCKED_PATTERNS = [
    'rm -rf /', 'sudo', 'curl.*|.*sh', '> /dev/', 
    'chmod 777', 'wget.*|.*sh',
]

def main():
    input_data = json.loads(sys.stdin.read())
    command = input_data.get('tool_input', {}).get('command', '')

    if not command:
        sys.exit(0)

    # 检查黑名单
    import re
    for pattern in BLOCKED_PATTERNS:
        if re.search(pattern, command):
            print(f"🚫 危险命令被拦截: {command}", file=sys.stderr)
            print(f"   匹配禁止模式: {pattern}", file=sys.stderr)
            sys.exit(1)

    # 检查白名单
    allowed = any(command.strip().startswith(cmd) for cmd in ALLOWED_COMMANDS)
    if not allowed:
        print(f"⚠️ 命令不在白名单中: {command}", file=sys.stderr)
        print(f"   白名单: {', '.join(ALLOWED_COMMANDS)}", file=sys.stderr)
        # 根据需要决定是拦截还是警告
        # sys.exit(1)  # 严格模式:拦截
        sys.exit(0)     # 宽松模式:警告后放行

    sys.exit(0)

if __name__ == '__main__':
    main()

完整配置:三层闸门一起上

将以上三个 Hook 合并到 .claude/settings.json

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "command": "python3 .claude/hooks/bash_guard.py"
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "command": "python3 .claude/hooks/validate_file.py"
      }
    ],
    "Stop": [
      {
        "matcher": "",
        "command": "python3 .claude/hooks/quality_gate.py"
      }
    ]
  }
}

效果:Agent 从"随意操作"变成"受控流水线"。

进阶技巧

1. 与外部评分 API 集成

Stop Hook 不只是本地检查,还可以调用外部评分服务:

import urllib.request, urllib.parse

# 调用我们自己的文章评分 API
def rate_with_api(text: str) -> dict:
    url = "https://your-api.com/rate-article"
    data = json.dumps({"content": text}).encode()
    req = urllib.request.Request(url, data=data, 
        headers={"Content-Type": "application/json"})
    resp = urllib.request.urlopen(req, timeout=30)
    return json.loads(resp.read())

score = rate_with_api(text)
if score['total'] < 60:
    print(f"评分 {score['total']}/100,不达标", file=sys.stderr)
    sys.exit(1)

2. Hook 链式调用

多个 Stop Hook 按数组顺序依次执行,前一个失败会阻止后续执行:

"Stop": [
  {"matcher": "", "command": "python3 .claude/hooks/quality_gate.py"},
  {"matcher": "", "command": "python3 .claude/hooks/plagiarism_check.py"},
  {"matcher": "", "command": "python3 .claude/hooks/seo_check.py"}
]

3. 条件匹配

matcher 支持正则,可以让 Hook 只在特定场景触发:

"PostToolUse": [
  {
    "matcher": "Write.*\\.py$",
    "command": "python3 .claude/hooks/validate_python.py"
  },
  {
    "matcher": "Write.*\\.md$",
    "command": "python3 .claude/hooks/validate_markdown.py"
  }
]

一人公司的实际收益

我们在 AI创业内参 的内容流水线中部署了这套三层闸门后:

指标 部署前 部署后 提升
文章合格率(一次通过) ~55% 92% +67%
人工复核时间/篇 8分钟 2分钟 -75%
草稿箱残次品率 30% 3% -90%
日均发布量 2.3篇 4.1篇 +78%

核心逻辑:不是让 AI 更聪明,而是给它一套"错了就重来"的自动化纠错机制。

立即行动

  1. 在项目根目录创建 .claude/ 文件夹
  2. 复制上面的 settings.json 和三个 hook 脚本
  3. 修改 quality_gate.py 中的字数阈值和章节要求,匹配你的标准
  4. 运行一次 Claude Code 让它写篇文章,观察 Hook 是否生效
  5. 根据第一次运行结果调整检查规则

15分钟就能搭好这套闸门,之后每次AI产出都会自动质检。


Agent工坊 #ClaudeCode #AI自动化 #一人公司 #质量管控