BeforeCommand Hook — 命令执行前触发

Claude Code Hooks 详解 · 自动化事件响应

一、概述

BeforeCommand 是 Claude Code 提供的一种事件钩子(Hook)机制,它在每次 CLI 命令即将执行之前被触发。该钩子允许开发者在命令实际运行之前注入自定义逻辑,从而实现环境检查、权限预验证、输入清理、审计日志记录等操作。BeforeCommand 钩子是 Claude Code 自动化工作流中不可或缺的安全与治理组件,尤其适用于团队协作环境和企业级部署场景。

与 BeforeCommand 相对应的钩子是 AfterCommand,它在命令执行完成之后触发。两者配合使用可以构建完整的命令生命周期管理机制。BeforeCommand 钩子的核心价值在于"预防"——在问题发生之前进行拦截和处理,而不是事后回溯。这种前置拦截模式极大地提升了操作安全性和可审计性。

核心特性: BeforeCommand 钩子是一个同步钩子,它会阻塞命令的执行直到钩子脚本运行完毕并返回结果。这意味着钩子脚本必须快速完成,以避免影响用户体验。如果钩子返回非零退出码,对应的命令将被阻止执行。

二、配置方式

BeforeCommand 钩子通过 Claude Code 的配置文件进行注册。该配置文件通常位于项目根目录的 .claude/settings.json 中,或是用户级别的全局配置文件中。钩子定义在 hooks 字段下的 BeforeCommand 属性中,该属性对应一个指向可执行脚本或命令的字符串。

配置格式如下:

{ "hooks": { "BeforeCommand": "node .claude/hooks/before-command.mjs" } }

上述配置表示:在每次命令执行之前,Claude Code 都会调用 node .claude/hooks/before-command.mjs 这个脚本。该脚本会接收到当前即将执行的命令信息,并基于此做出相应的处理决策。

除了使用 Node.js 脚本,你也可以使用任何可执行文件作为钩子脚本,包括 Shell 脚本、Python 脚本、编译后的二进制程序等,只要系统能够正确识别并调用该文件。钩子脚本应当被赋予可执行权限(Unix 系统使用 chmod +x)。

提示

建议将钩子脚本放置在项目目录下的 .claude/hooks/ 文件夹中,这样既可以与项目代码一同进行版本管理,又能够保持项目结构的整洁性。同时,将钩子纳入版本控制可以确保所有团队成员使用统一的钩子规则。

三、钩子脚本的执行环境

BeforeCommand 钩子脚本在调用时,Claude Code 会通过环境变量向脚本传递当前命令的上下文信息。理解这些环境变量是利用钩子机制进行精细化控制的基础。

环境变量说明示例值
CLAUDE_HOOK_COMMAND用户输入的原始完整命令git push origin main
CLAUDE_HOOK_ARGS命令的参数列表(以空格分隔的字符串)push origin main
CLAUDE_HOOK_CWD命令执行时的工作目录/home/user/project
CLAUDE_HOOK_RESULT通常为空,保留用于扩展

通过这些环境变量,钩子脚本可以精确地了解当前即将执行的命令是什么、在哪个目录下执行、以及携带了哪些参数。这些信息为后续的逻辑判断提供了充分的上下文支撑。

// 示例:在钩子脚本中读取环境变量 const command = process.env.CLAUDE_HOOK_COMMAND; const args = process.env.CLAUDE_HOOK_ARGS; const cwd = process.env.CLAUDE_HOOK_CWD; console.log(`[BeforeCommand] 即将执行: ${command}`); console.log(`[BeforeCommand] 工作目录: ${cwd}`);

四、返回值与命令拦截机制

BeforeCommand 钩子脚本的退出码(exit code)决定了命令是否继续执行。这是钩子机制中最关键的行为控制点,理解这一机制可以帮助你构建精细化的命令管控策略。

退出码含义执行结果
0允许执行命令正常执行
非 0拒绝执行命令被阻止,用户看到拒绝提示

当钩子脚本返回非零退出码时,Claude Code 会向用户展示一条拒绝消息,表明当前命令被钩子规则阻止执行。需要注意的是,钩子脚本的标准输出(stdout)和标准错误(stderr)内容会显示给用户,因此你可以在脚本中提供友好的拒绝原因说明。

重要提醒

钩子脚本的退出码判断优先级高于脚本本身产生的任何输出。即使脚本输出了警告信息,只要退出码为 0,命令仍会被放行;反之,即使脚本没有任何输出,只要退出码非 0,命令就会被阻止。请务必在脚本的每个分支中显式设置正确的退出码。

// 正确的退出码处理示例 function shouldAllow(command) { // 自定义逻辑... return true; // 或 false } const command = process.env.CLAUDE_HOOK_COMMAND; if (shouldAllow(command)) { process.exit(0); // 允许执行 } else { console.error("命令被阻止:安全策略不允许此操作。"); process.exit(1); // 拒绝执行 }

五、核心应用场景

5.1 环境合规性检查

在命令执行之前验证当前环境是否满足必要的条件。例如,检查 Node.js 版本是否满足项目要求、必要的配置文件是否存在、环境变量是否已正确设置等。这种检查可以避免因环境不一致导致的运行时错误,对于多人协作项目尤为重要。

// 检查 Node.js 版本 const nodeVersion = process.version; const required = ">=18.0.0"; if (compareVersions(nodeVersion, required) < 0) { console.error(`Node.js 版本不足: 当前为 ${nodeVersion},需要 ${required}`); process.exit(1); } process.exit(0);

5.2 敏感操作白名单机制

对可能产生破坏性后果的敏感命令进行白名单控制。例如,只允许在特定分支上执行 git push --force,或者要求生产环境的数据库变更命令必须附带特定的确认标志。这种机制为高风险操作提供了额外的安全层。

// 敏感命令保护示例 const command = process.env.CLAUDE_HOOK_COMMAND; // 检测强制推送操作 if (command.includes("git push --force")) { const branch = getCurrentBranch(); if (branch === "main" || branch === "production") { console.error("禁止在 main/production 分支上执行强制推送。"); process.exit(1); } } process.exit(0);

5.3 审计日志记录

在每次命令执行之前将命令信息记录到持久化日志文件中。审计日志是安全合规和问题回溯的重要依据,它可以帮助团队追踪"谁在什么时间执行了什么命令"。结合 AfterCommand 钩子还可以记录命令的执行结果和耗时。

// 审计日志记录 const command = process.env.CLAUDE_HOOK_COMMAND; const cwd = process.env.CLAUDE_HOOK_CWD; const timestamp = new Date().toISOString(); const user = process.env.USER || process.env.USERNAME || "unknown"; const logLine = `[${timestamp}] user=${user} cwd=${cwd} cmd=${command}\n`; const fs = require("fs"); fs.appendFileSync(".claude/audit.log", logLine); process.exit(0); // 仅记录,不阻止执行

5.4 输入消毒与安全检查

对用户输入的命令进行安全检查,防止危险操作或不合规命令的执行。例如,检测命令中是否包含硬编码的敏感信息(密码、API 密钥等),或者是否尝试访问未授权的文件路径。

// 检测命令中是否包含潜在的敏感信息 const command = process.env.CLAUDE_HOOK_COMMAND; const sensitivePatterns = [ /password=/, /api[_-]?key=/, /secret=/i, /--password\s+\S+/, /--token\s+\S+/ ]; for (const pattern of sensitivePatterns) { if (pattern.test(command)) { console.error("命令中包含敏感信息,为确保安全已阻止执行。"); process.exit(1); } } process.exit(0);

5.5 限速与资源管控

对命令的执行频率进行控制,防止短时间内大量重复执行同一操作。这在 CI/CD 环境中尤为重要,可以避免因误操作导致的资源浪费或服务过载。

// 简单的速率限制示例 const command = process.env.CLAUDE_HOOK_COMMAND; const RATE_LIMIT_FILE = ".claude/.rate_limit"; const fs = require("fs"); const now = Date.now(); try { const lastExec = parseInt(fs.readFileSync(RATE_LIMIT_FILE, "utf8"), 10); if (now - lastExec < 2000) { // 2秒内不允许重复执行 console.error("操作过于频繁,请稍后重试。"); process.exit(1); } } catch (e) { /* 首次执行,无需检查 */ } fs.writeFileSync(RATE_LIMIT_FILE, now.toString()); process.exit(0);

六、完整脚本示例

以下是一个功能完整的 BeforeCommand 钩子脚本示例,它综合了环境检查、敏感操作保护和审计日志记录等多项功能。该脚本使用 Node.js 编写,可以直接作为实际项目中的钩子脚本使用。

#!/usr/bin/env node // .claude/hooks/before-command.mjs // BeforeCommand 钩子脚本 - 综合安全控制 import fs from "fs"; import path from "path"; import { fileURLToPath } from "url"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const command = process.env.CLAUDE_HOOK_COMMAND || ""; const cwd = process.env.CLAUDE_HOOK_CWD || process.cwd(); // 1. 审计日志 const logDir = path.join(__dirname, "..", "logs"); fs.mkdirSync(logDir, { recursive: true }); const logEntry = `[${new Date().toISOString()}] ${command}\n`; fs.appendFileSync(path.join(logDir, "commands.log"), logEntry); // 2. 危险命令黑名单 const dangerousPatterns = [ /^rm\s+-rf\s+\/$/, // 删除根目录 /^rm\s+-rf\s+\/home/, // 删除用户目录 /^dd\s+if=/, // 原始设备写入 /^>\/dev\/sda/, // 直接写入块设备 /:\/\/[^@]+:[^@]+@/, // URL 中包含明文密码 ]; for (const pattern of dangerousPatterns) { if (pattern.test(command)) { console.error(`[安全拦截] 命令 "${command}" 被禁止执行。`); console.error("原因:匹配到危险操作模式,可能存在系统安全风险。"); process.exit(1); } } // 3. git 操作保护 if (command.startsWith("git push")) { const currentBranch = execSync("git rev-parse --abbrev-ref HEAD", { encoding: "utf8" }).trim(); if (["main", "master", "production"].includes(currentBranch)) { if (command.includes("--force") || command.includes("-f")) { console.error(`[安全拦截] 禁止向 ${currentBranch} 分支执行强制推送。`); process.exit(1); } console.log(`[提示] 正在向受保护分支 "${currentBranch}" 推送,请确认操作正确。`); } } // 4. 运行时间限制(防止下班后执行危险操作) const hour = new Date().getHours(); if (hour >= 22 || hour < 6) { if (command.includes("deploy") || command.includes("release")) { console.error("[安全拦截] 非工作时间禁止执行部署/发布操作。"); process.exit(1); } } // 5. 放行 process.exit(0);

七、最佳实践

7.1 保持钩子脚本轻量高效

BeforeCommand 钩子是一个同步阻塞钩子,它会延迟命令的实际执行。因此,钩子脚本应当尽可能轻量快速,避免执行耗时的操作(如网络请求、大规模文件扫描等)。如果确实需要执行耗时操作,考虑引入缓存机制或异步预处理。一般来说,钩子脚本的执行时间不应超过 100 毫秒。

7.2 提供清晰的反馈信息

当钩子脚本阻止命令执行时,应当提供明确且友好的失败原因说明,帮助用户理解为什么操作被拒绝以及如何修正。模糊的错误信息(如 "Operation not allowed")会降低用户体验,增加排查困难。

7.3 分级控制策略

根据项目的不同环境(开发、测试、生产)设置不同严格程度的钩子规则。例如,在开发环境中只做日志记录而不过度限制;在测试环境中增加基本的合规检查;在生产环境中执行最严格的安全管控。这种分级策略可以在安全性和开发效率之间取得平衡。

7.4 结合 AfterCommand 使用

BeforeCommand 和 AfterCommand 是一对相辅相成的钩子。建议将前置检查放在 BeforeCommand 中,将结果汇总、性能统计和后续清理操作放在 AfterCommand 中。例如,BeforeCommand 记录命令开始时间,AfterCommand 计算执行耗时并写入完整审计记录。

7.5 充分测试钩子脚本

钩子脚本直接影响用户的日常操作流程,因此必须经过充分测试。建议编写单元测试覆盖各种命令场景,并在测试环境中验证钩子行为符合预期。特别注意测试边界情况:当钩子脚本本身出错时(如崩溃、超时),系统应当优雅地处理异常,而不是影响正常操作。

最佳实践总结: 轻量快速 + 明确反馈 + 分级策略 + 配对使用 + 充分测试。遵循这五项原则可以构建一个既安全又不影响开发效率的钩子系统。

八、常见问题与故障排除

8.1 钩子脚本未执行

如果 BeforeCommand 钩子完全没有被触发,请检查以下方面:配置文件中的 hooks.BeforeCommand 路径是否正确;对应的脚本文件是否存在于指定路径;文件是否具有可执行权限(Unix 系统需通过 chmod +x 添加执行权限);配置文件格式是否为有效的 JSON。

8.2 钩子执行失败导致所有命令被阻止

如果钩子脚本自身存在语法错误或运行时异常,可能会导致退出码为非零值,从而无差别地阻止所有命令的执行。此时应检查钩子脚本的运行日志,修复脚本错误。作为一种应急措施,可以临时注释掉 settings.json 中的 BeforeCommand 配置以恢复系统正常运行。

8.3 环境变量为空

在某些特殊环境中,CLAUDE_HOOK_COMMAND 等环境变量可能未正确传递。这通常与操作系统的环境变量传递机制有关。建议在钩子脚本中添加调试输出,将接收到的环境变量转储到日志文件中进行排查。

8.4 性能影响

如果用户感觉命令执行有明显的延迟,可能是钩子脚本执行了耗时操作。请检查钩子脚本中是否存在不必要的 I/O 操作、网络请求或大规模计算。考虑将耗时结果进行缓存,或优化脚本逻辑减少不必要的操作。

故障恢复步骤

当钩子脚本导致系统无法正常使用时,可以按照以下步骤恢复:1) 打开项目目录下的 .claude/settings.json 文件。2) 移除或注释 "BeforeCommand" 配置行。3) 保存文件并重启 Claude Code 会话。4) 待钩子脚本修复后重新启用配置。

九、高级用法与扩展

9.1 多条件复合判断

在实际项目中,钩子逻辑往往需要在多个维度上进行复合判断。例如,根据当前工作目录、命令类型和用户角色三个维度综合决定是否放行命令。这种复合判断可以通过规则引擎或决策树模式来实现。

// 复合决策规则引擎示例 const rules = [ { pattern: /git push/, branch: "main", action: "warn" }, { pattern: /rm -rf/, cwd: "/etc", action: "deny" }, { pattern: /npm publish/, action: "confirm" }, { pattern: /deploy/, hour: [0, 6], action: "deny" }, ]; function evaluateRules(command, cwd) { for (const rule of rules) { if (!rule.pattern.test(command)) continue; if (rule.cwd && !cwd.startsWith(rule.cwd)) continue; if (rule.branch && getCurrentBranch() !== rule.branch) continue; if (rule.hour) { const h = new Date().getHours(); if (h < rule.hour[0] || h >= rule.hour[1]) continue; } return rule.action; } return "allow"; }

9.2 与外部服务集成

高级场景中,BeforeCommand 钩子可以与外部服务(如鉴权中心、密钥管理服务 KMS、配置中心等)进行集成。例如,在执行敏感操作前向 OAuth 服务请求授权,或从密钥管理服务获取临时的访问凭证。需要注意的是,与外部服务的集成必须考虑网络延迟和可用性问题。

// 与外部鉴权服务集成的伪代码 async function checkWithAuthService(command, user) { // 避免在钩子中直接使用同步网络请求 // 考虑使用缓存 + 异步预加载策略 const cached = cache.get(`${user}:${command}`); if (cached) return cached; // 实际场景中请使用异步方式,此处仅为示意 const response = await fetchAuthService(command, user); cache.set(`${user}:${command}`, response.allowed); return response.allowed; }

9.3 分环境配置管理

通过检测当前环境自动切换钩子规则的严格程度,是实现环境感知钩子的常用模式。例如,通过检查 NODE_ENV、是否存在特定的环境标记文件或域名后缀来判断当前环境并应用对应的规则集。

// 环境感知配置切换 const env = process.env.NODE_ENV || "development"; const policies = { development: { logOnly: true, blockDangerous: false, }, staging: { logOnly: false, blockDangerous: true, requireConfirm: true, }, production: { logOnly: false, blockDangerous: true, requireConfirm: true, blockNonBusinessHours: true, }, }; const policy = policies[env] || policies.development;

十、总结

BeforeCommand 钩子是 Claude Code 提供的一种强大的命令前置拦截机制,它为开发者提供了在命令执行之前注入自定义逻辑的能力。通过合理运用 BeforeCommand 钩子,可以实现环境合规性检查、敏感操作保护、审计日志记录、输入消毒、速率控制等多种安全与治理功能。

理解并掌握 BeforeCommand 钩子的配置方式、退出码语义、环境变量传递机制和最佳实践,是充分发挥 Claude Code 自动化能力的关键。在实际使用中,应当注意保持钩子脚本的轻量高效,提供清晰的用户反馈,并结合 AfterCommand 钩子构建完整的命令生命周期管理方案。

随着团队规模的扩大和项目复杂度的提升,BeforeCommand 钩子将成为 Claude Code 工作流中不可或缺的组成部分。建议在项目初期就建立统一的钩子管理规范,将钩子脚本纳入版本控制,并在团队内普及钩子的配置和使用方法。

核心要点回顾: 1) 在 .claude/settings.json 中配置。2) 通过 CLAUDE_HOOK_COMMAND 环境变量获取命令信息。3) 退出码 0 放行,非 0 阻止。4) 脚本必须轻量快速。5) 最佳实践:环境检查、安全保护、审计日志、限速控制。6) 结合 AfterCommand 实现完整生命周期管理。