Advanced (更新: 2026/6/1)

Claude Code Hooks入门:用自动检查减少误操作

面向初学者的Claude Code Hooks实务指南,覆盖危险命令拦截、日志保存、自动格式化与测试。

Claude Code Hooks入门:用自动检查减少误操作

Claude Code Hooks可以理解为“在Claude开始工作前后自动运行的检查”。你不需要每次都提醒它“不要执行危险命令”“改完代码记得format”“最后跑一下测试”,而是把这些规则放进Claude Code的生命周期里。

对初学者来说,最重要的不是记住所有事件名称,而是分清四个位置:PreToolUse 是刹车,PostToolUse 是维修和验证,UserPromptSubmit 是接待记录,Stop 是离开前的最终确认。Hooks不会替代人工review、权限设置或CI,但它能把容易忘记的重复检查固定下来。

本文基于 Claude Code Hooks官方参考Claude Code settings官方文档 编写。项目上下文的整理可以继续看 CLAUDE.md最佳实践,权限整体设计可以配合 Claude Code权限指南 阅读。

先理解4个最常用事件

Claude Code的Hook事件很多,但实际落地时,先掌握下面四个就够了。

事件适合做什么例子
UserPromptSubmit用户提示词交给Claude之前保存请求、检查是否误贴密钥、补充轻量上下文
PreToolUse工具执行之前阻止危险Bash命令、本番环境操作、删除命令
PostToolUse工具执行成功之后自动format、lint、运行相关测试
StopClaude准备结束回复时检查Git冲突、保存总结、提醒缺少验证

如果一个动作“绝对不能发生”,放在 PreToolUse。如果动作已经发生,需要整理或验证,放在 PostToolUse。如果你想分析用户请求质量,用 UserPromptSubmit。如果你想避免会话带着明显问题结束,用 Stop

共享规则通常写在项目的 .claude/settings.json。个人实验可以放在 .claude/settings.local.json,避免提交到仓库。团队使用时要在README里写清楚:这些Hook由谁维护、哪些会阻塞、哪些只记录日志。

可复制的最小配置

下面的配置包含四件事:保存提示词日志、阻止危险Bash命令、编辑后执行质量检查、结束时保存简要状态。先从小配置开始,比一口气自动化所有流程更安全。

{
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "node .claude/hooks/log-prompt.mjs"
          }
        ]
      }
    ],
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "node .claude/hooks/block-dangerous-command.mjs"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "node .claude/hooks/run-quality-checks.mjs",
            "timeout": 120
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "node .claude/hooks/stop-summary.mjs"
          }
        ]
      }
    ]
  }
}

注意这里的结构是“事件、matcher组、handler数组”。也就是说,真正运行的命令在内部的 hooks 数组里,并且要写 type: "command"。网上还能看到一些把 command 直接放在 matcher 旁边的旧例子,初学者最好不要照抄。

Use case 1:用PreToolUse阻止危险命令

第一个实际场景是安全刹车。当Claude Code快速修改项目时,最危险的不是命令执行完之后,而是命令即将执行之前。PreToolUse 可以读取Bash输入,在明显危险时拒绝执行。

保存为 .claude/hooks/block-dangerous-command.mjs

import fs from "node:fs";

const input = JSON.parse(fs.readFileSync(0, "utf8") || "{}");
const command = String(input.tool_input?.command || "");

const denyPatterns = [
  /rm\s+-rf\s+(\/|\*|\.|\$HOME)/i,
  /cat\s+\.env(\.|$|\s)/i,
  /printenv/i,
  /aws\s+.*\s+delete-/i,
  /gcloud\s+.*\s+delete/i,
  /kubectl\s+delete\s+(namespace|deployment|secret)/i,
  /DROP\s+DATABASE/i
];

const matched = denyPatterns.find((pattern) => pattern.test(command));

if (matched) {
  console.log(JSON.stringify({
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      permissionDecision: "deny",
      permissionDecisionReason: `Blocked by project hook: ${matched}`
    }
  }));
  process.exit(0);
}

这个脚本不是完整的安全产品。正则表达式无法覆盖所有绕过方式。但是它能拦住最常见、最吓人的错误:误删大目录、读取 .env、删除云资源、删除Kubernetes资源、执行破坏性SQL。真正的安全设计还需要结合Claude Code权限、Git保护分支、CI和人工审批。

Use case 2:用UserPromptSubmit保存请求日志

第二个场景是记录提示词。很多Claude Code失败并不是模型能力不够,而是一开始的请求太模糊,例如“修一下”“整理一下”“改得好一点”。如果没有日志,团队很难复盘是哪种请求导致结果变差。

保存为 .claude/hooks/log-prompt.mjs

import fs from "node:fs";
import path from "node:path";

const input = JSON.parse(fs.readFileSync(0, "utf8") || "{}");
const dir = path.join(process.cwd(), ".claude", "hook-logs");
fs.mkdirSync(dir, { recursive: true });

const record = {
  time: new Date().toISOString(),
  event: input.hook_event_name,
  cwd: input.cwd,
  prompt: input.prompt || input.user_prompt || ""
};

fs.appendFileSync(
  path.join(dir, "prompts.jsonl"),
  JSON.stringify(record) + "\n",
  "utf8"
);

日志先保存在本地,不要急着发送到外部服务。提示词里可能包含客户名、内部URL、API Key、业务信息。把 .claude/hook-logs/ 加到 .gitignore,并且规定保存期限和访问权限,是团队采用日志前必须做的事情。

Use case 3:编辑后自动format和test

第三个场景是日常质量检查。Claude改完文件后,自动运行formatter和小范围测试。这样你不用每次在提示词最后补一句“顺便format并跑测试”。

保存为 .claude/hooks/run-quality-checks.mjs

import fs from "node:fs";
import { execFileSync } from "node:child_process";

const input = JSON.parse(fs.readFileSync(0, "utf8") || "{}");
const filePath = String(input.tool_input?.file_path || "");

const isSourceFile = /\.(js|jsx|ts|tsx|css|md|mdx|json)$/.test(filePath);
if (!isSourceFile) process.exit(0);

const run = (cmd, args) => {
  try {
    return execFileSync(cmd, args, {
      cwd: process.cwd(),
      encoding: "utf8",
      stdio: ["ignore", "pipe", "pipe"]
    });
  } catch (error) {
    return String(error.stdout || "") + String(error.stderr || "");
  }
};

const messages = [];
messages.push(run("npx", ["prettier", "--write", filePath]));

if (/\.(js|jsx|ts|tsx)$/.test(filePath)) {
  messages.push(run("npm", ["test", "--", "--runInBand"]));
}

console.log(JSON.stringify({
  hookSpecificOutput: {
    hookEventName: "PostToolUse",
    additionalContext: messages.join("\n").slice(-4000)
  }
}));

大仓库不要每次都跑全量测试。那会让Hooks变成负担。先只做format,再逐步增加相关测试,必要时用 async: true 让重任务在后台运行。返回给Claude的输出也要截断,否则日志会挤占上下文。

Use case 4:Stop阶段检查是否可以结束

Stop 在Claude准备结束回复时触发。这里适合保存总结,或者只阻止非常明确的未完成状态,比如Git冲突还没解决。

import fs from "node:fs";
import { execFileSync } from "node:child_process";

const input = JSON.parse(fs.readFileSync(0, "utf8") || "{}");

if (input.stop_hook_active) {
  process.exit(0);
}

let status = "";
try {
  status = execFileSync("git", ["status", "--short"], {
    cwd: process.cwd(),
    encoding: "utf8"
  });
} catch {
  process.exit(0);
}

if (status.includes("UU ")) {
  console.error("Git conflict remains. Resolve conflicts before finishing.");
  process.exit(2);
}

fs.mkdirSync(".claude/hook-logs", { recursive: true });
fs.appendFileSync(
  ".claude/hook-logs/stop.jsonl",
  JSON.stringify({
    time: new Date().toISOString(),
    lastAssistantMessage: input.last_assistant_message || "",
    gitStatus: status.slice(0, 2000)
  }) + "\n"
);

Stop hook最容易写得太严格。如果条件不清楚,Claude会反复尝试结束又被拦住。实际运用时要检查 stop_hook_active,只阻止明确可以修复的问题,其余情况以记录日志为主。

pitfall:常见失败和回避方法

第一个pitfall是把Hooks当成权限系统。Command hook会以你的系统用户权限运行,写错脚本同样可以删除文件或读取敏感信息。输入必须校验,路径必须处理,.env.git、密钥文件默认不要碰。

第二个pitfall是同步执行太多任务。编辑一个文件后格式化可以接受,但每次都build、全量test、浏览器测试、部署,就会拖慢Claude。快检查放在 PostToolUse,慢检查交给异步Hook或CI。

第三个pitfall是没有日志策略。日志能帮助复盘,但也可能保存秘密信息。决定保存位置、保存期限、谁能读取、是否进Git,比写脚本本身更重要。

第四个pitfall是误解matcher。matcher: "Bash" 只是让Bash事件进入handler,并不代表所有Bash都安全。真正的判断仍然要在脚本里完成。

Masa式验证笔记

实际试下来,效果最明显的是把format和test结果通过 additionalContext 还给Claude。Claude下一步就知道该修什么,不需要人手动复制日志。危险命令拦截也很有价值,因为它在最关键的执行前一刻拦住明显事故。

如果是第一次在团队里导入,我会按三步走:先记录一周提示词,再开启编辑后format,最后增加少量禁止Bash规则。这样有数据、有反馈,也不会一开始就让自动化变得烦人。

需要把权限、CLAUDE.md、Hooks、review流程一起整理时,可以看 Claude Code培训页面。个人开发者也可以把这套workflow整理成Gumroad清单,作为每个新项目的启动模板。

总结

Claude Code Hooks不是炫技,而是让AI开发更稳定的护栏。用 UserPromptSubmit 理解请求,用 PreToolUse 阻止危险动作,用 PostToolUse 验证修改,用 Stop 避免带着明显问题结束。

先小规模开始,再根据日志增加规则。一个简单、稳定、大家愿意保留的Hook,比复杂但两天后被禁用的自动化更有价值。

#Claude Code #Hooks #automation #security #testing
免费

免费 PDF: Claude Code 速查表

输入邮箱即可获取一页 PDF,整理常用命令、审查习惯和安全工作流。

我们会妥善保护你的信息,不发送垃圾邮件。

把 Claude Code 变成真正能带来结果的工作流

先领取中文说明的免费 PDF,再进入英文商品页选择合适的教材。如果你需要团队落地、流程设计或内容变现支持,也可以直接咨询。

Masa

关于作者

Masa

专注 Claude Code 实务流程、团队导入和内容转化的工程师。