设计Agent 生成代码的 Lint 规则体系,理解 Hook 机制
上一章我们建立了一个关键认知:Rules 文件是概率性的约束——Agent "大概率"会遵守,但不保证。当你告诉 Agent “不要硬编码 API Key”,它在 99 次调用中可能都遵守了,但第 100 次可能因为上下文过长或注意力分散而忽略这条规则。
在 In the Loop 模式中,人类审查是最后的安全网。但在 On the Loop 模式中,我们不希望依赖人类审查——我们希望有一个确定性的机制来自动拦截违规代码。
这就是Hook的价值所在。Hook 是挂载在 Agent 生命周期事件上的确定性脚本——当 Agent 试图执行某个操作时,Hook 脚本会被触发,如果检查不通过,操作会被直接阻止,Agent 无法跳过。
“Hooks guarantee execution; prompts do not.”
—— blakecrosley.com, Agent Architecture Guide
核心概念
传统 Lint 与 Agent Lint 的区别
传统的 Lint 工具(ESLint、Pylint、golangci-lint)是为人类开发者设计的——它们检查代码风格、潜在 Bug、未使用的变量等。当代码不合规时,Lint 会给出警告,但开发者可以选择忽略。
Agent Lint 需要解决一类不同的问题:
| 维度 | 传统 Lint | Agent Lint |
|---|---|---|
| 检查对象 | 人类写的代码 | AI 生成的代码 |
| 关注重点 | 代码风格、最佳实践 | 安全、权限、架构边界 |
| 执行方式 | 开发者手动运行或 CI | 每次操作自动触发,不可跳过 |
| 失败处理 | 警告或错误 | exit code 2 直接阻止操作 |
| 检查时机 | 代码写完后 | 代码写之前(PreToolUse)和写之后(PostToolUse) |
Agent Lint 的核心差异在于执行时机和不可跳过性:
- PreToolUse:在 Agent 执行工具(如 Bash、Write)之前检查——可以在代码生成之前就拦截危险操作
- 不可跳过:exit code 2 会直接阻止操作,Agent 没有办法绕过
Hook 架构:Agent 生命周期的 12+ 个事件
Claude Code 定义了一组完整的生命周期事件,每个事件都可以挂载 Hook:
| Hook 事件 | 触发时机 | 能否阻止? | 最佳用途 |
|---|---|---|---|
| SessionStart | 会话开始 | 否 | 加载上下文、设置环境变量 |
| UserPromptSubmit | 用户发送消息 | 是 | 上下文注入、输入验证 |
| PreToolUse | 工具执行之前 | 是 | 安全阻止、自动审批 |
| PermissionRequest | 权限对话框出现 | 是 | 自动审批/拒绝 |
| PostToolUse | 工具成功执行后 | 否* | 自动格式化、Lint、日志 |
| PostToolUseFailure | 工具执行失败后 | 否 | 错误处理 |
| SubagentStart | 子 Agent 启动 | 否 | 子 Agent 初始化 |
| SubagentStop | 子 Agent 结束 | 是 | 子 Agent 验证 |
| Stop | Agent 完成响应 | 是 | 任务完成验证 |
| PreCompact | 上下文压缩前 | 否 | 会话记录备份 |
| Setup | –init/–maintenance | 否 | 一次性设置 |
| SessionEnd | 会话结束 | 否 | 清理、日志记录 |
*PostToolUse 可以向 Agent 提供反馈,但无法撤销已执行的操作。
最核心的两个 Hook 是PreToolUse和PostToolUse:
- PreToolUse:在 Agent 执行操作之前运行检查脚本。如果脚本返回 exit code 2,操作被阻止,stderr 的内容会发送给 Agent 作为反馈。
- PostToolUse:在 Agent 成功执行操作之后运行处理脚本。常用于自动格式化、Lint 检查、审计日志。
Exit Code:控制机制
Hook 通过 exit code 与 Harness 运行时通信:
| Exit Code | 含义 | 行为 |
|---|---|---|
| 0 | 成功 | Hook 正常运行,stdout 被处理 |
| 2 | 阻止 | 操作被停止,stderr 发送给 Agent |
| 其他 | 错误 | stderr 展示给用户,执行继续 |
Exit code 2 是你的核心武器。一个返回 exit code 2 的 PreToolUse Hook 可以阻止任何危险操作。一个返回 exit code 2 的 Stop Hook 可以强制 Agent 继续工作。
原理剖析
Hook 配置结构
Hook 在.claude/settings.json中配置:
{"hooks":{"PreToolUse"