跳到正文

多 AI Coding Agent 单源配置:从 AGENTS.md 到跨端 Hooks

大杂烩6 min
目录(9)

团队同时使用 Claude Code、Cursor、Codex 后,很容易出现一种隐蔽的工程债: 每个工具都有一套规则文件,内容看起来差不多,却在数周后悄悄分叉。

常见症状有三个:

  • CLAUDE.md、Cursor Rules、AGENTS.md 分别维护,规则彼此矛盾;
  • 所有说明堆进一个超长文件,每次对话都占用大量上下文;
  • 配置文件已经提交,但 Agent 实际没有加载,团队却误以为规则生效。

解决办法不是再写一份“更完整”的规则,而是明确三件事:

  1. 每类内容只有一个事实源;
  2. 不同 Agent 的配置只做桥接和协议转换;
  3. 能机械判断的规则交给 Hooks 与 CI,而不是期待模型自觉遵守。

本文行为基线核对日期为 2026-07-31。Coding Agent 更新很快,正式采用前应重新核对官方文档。

单一事实源不是单一文件

SSOT(Single Source of Truth)强调的是内容只维护一份,不要求整个仓库只能有一个配置文件。

一个可维护的目录可以这样组织:

project/
├── AGENTS.md                         # 全局项目约定
├── CLAUDE.md                         # Claude Code 桥接:@AGENTS.md
├── .agents/
│   ├── skills/
│   │   └── domain-workflow/SKILL.md # 按需加载的领域流程
│   └── hooks/
│       ├── core/                    # 跨端共享策略
│       └── adapters/                # 各产品 JSON 协议适配
├── .claude/settings.json            # Claude Code hooks 配置
├── .codex/hooks.json                # Codex hooks 配置
└── .cursor/hooks.json               # Cursor hooks 配置

这里有三类实质内容:

  • AGENTS.md:每次工作都需要知道的命令、架构边界与完成定义;
  • .agents/skills/:只在特定任务出现时才需要的操作流程;
  • .agents/hooks/core/:可以实时机械判断的策略。

其余文件只是入口、链接或适配器,不应复制业务规则。

确定性挂载与概率性发现

这两个概念经常被混在一起:

  • 路径规则的 globs / paths 是路径驱动的确定性挂载;
  • Skill 的 description 帮助模型判断当前任务是否相关,但自动触发仍是概率性的;
  • 明确点名一个 Skill 比等待自动触发可靠;
  • CI 才是违反规则后一定失败的确定性门禁。

所以 SKILL.mddescription 应描述“什么时候使用”,而不是只解释“它是什么”:

---
name: api-routes
description: 在 src/server/routes/ 下新增或修改接口、校验 schema 或错误响应时使用。
---

Agent Skills 是开放格式,核心结构就是一个包含 SKILL.md 的目录。Claude Code、Cursor、Codex 对技能目录的发现方式并不完全相同, 因此格式可以共享,入口仍可能需要桥接。

四层治理:Hooks、CI、全局规则、Skill

不是每条约束都应该写进 AGENTS.md。可以按下面的判定顺序分层:

全局

局部

一条待落地的约束

必须在危险动作发生前阻止吗?

Hooks

共享策略 + 各端适配器

能被编译器或 CI 判断吗?

CI / 类型检查 / Linter / 自定义扫描

全局恒真还是局部适用?

AGENTS.md

SKILL.md

例如:

约束推荐位置
禁止强制推送、禁止 --no-verifyPreToolUse / beforeShellExecution Hook
TypeScript 类型错误、格式不合规CI
项目不是 SSR、必须走指定数据入口AGENTS.md
新增文章时如何维护标签与 frontmatter写作 Skill

Hooks 可以被关闭、未信任或执行失败,因此它是快速护栏,不是安全边界;即使已经有 Hook, 能在 CI 中复核的规则仍应保留 CI 检查。

三端 Hooks 并不完全相同

Codex 和 Claude Code 在重叠事件上高度兼容:都使用 stdin JSON,shell 命令位于 tool_input.commandPreToolUse 可以返回 hookSpecificOutput,exit code 2 也可以阻止调用。

但“高度兼容”不等于“完全相同”。Claude Code 的事件集合更大,Codex 还有自己的扩展字段; Cursor 则使用另一套命名和 payload。

项目Claude CodeCodexCursor
项目配置.claude/settings.json.codex/hooks.jsonconfig.toml.cursor/hooks.json
shell 执行前PreToolUsePreToolUsebeforeShellExecution
命令字段tool_input.commandtool_input.commandcommand
拒绝字段permissionDecision: "deny"同 Claude Codepermission: "deny"
原因字段permissionDecisionReason同 Claude Codeagent_message
会话启动SessionStartSessionStartsessionStart

Cursor 当前使用 snake_case:agent_messageuser_messageadditional_context, 不能写成 agentMessagesessionStart 可以返回环境变量和附加上下文,但它是 fire-and-forget,不能用来阻塞会话启动。

各端细节可分别参考 Claude Code HooksCodex HooksCursor Hooks

共享策略核心

真正需要避免重复的是“什么命令危险”的判断,而不是三端 JSON 的形式。

// .agents/hooks/core/shell-policy.mjs
export function checkShell(command) {
	for (const rule of RULES) {
		if (rule.test(command)) {
			return { allow: false, code: rule.code, reason: rule.reason };
		}
	}
	return { allow: true };
}

策略核心不认识 Claude Code、Codex 或 Cursor。它只接收命令,返回判断结果。

Claude Code 与 Codex 可以共用一个 adapter:

const input = JSON.parse(await new Response(process.stdin).text());
const checked = checkShell(input.tool_input?.command);

if (!checked.allow) {
	console.log(
		JSON.stringify({
			hookSpecificOutput: {
				hookEventName: 'PreToolUse',
				permissionDecision: 'deny',
				permissionDecisionReason: checked.reason,
			},
		}),
	);
}

Cursor 单独处理顶层 command 和 snake_case 输出:

const input = JSON.parse(await new Response(process.stdin).text());
const checked = checkShell(input.command);

if (!checked.allow) {
	console.log(
		JSON.stringify({
			permission: 'deny',
			user_message: checked.reason,
			agent_message: checked.reason,
		}),
	);
}

三份配置文件只需要声明事件并指向 adapter。配置可以有多份,策略仍只有一份。

Codex Hooks 的两个特殊点

第一,项目级非托管 command hook 必须经过审查和信任。新增或修改 hook 定义后, 使用 /hooks 查看来源并确认状态,否则配置可能存在却被跳过。

信任绑定当前 hook 定义的哈希。如果 adapter 指向的脚本内容变化、配置命令却没有变化, 不要假设 Codex 一定会重新要求审查。团队仍应通过代码评审与 CI 检查脚本内容。

第二,同一事件下多个匹配的 command hook 会并发启动。一个 hook 无法阻止另一个启动, 因此不要把 hooks 设计成依赖执行顺序的流水线。

Codex 为插件携带的 hooks 提供 PLUGIN_ROOT / PLUGIN_DATA,并额外设置 CLAUDE_PLUGIN_ROOT / CLAUDE_PLUGIN_DATA 兼容已有 Claude 插件。 普通项目 hooks 不应依赖这些插件变量。

用 CI 检查 Hooks 自己

Hooks 也是代码,同样会漂移。至少应自动检查:

  1. 安全命令不会被误拦;
  2. 危险命令会命中正确规则;
  3. Claude Code / Codex adapter 输出 hookSpecificOutput
  4. Cursor adapter 输出 permissionagent_message
  5. 三份配置仍然指向共享 adapter。

然后把检查聚合到一个统一入口:

{
	"scripts": {
		"check:hooks": "bun run scripts/check-hooks.ts",
		"verify": "bun run lint && bun run check:types && bun run check:hooks && bun run build"
	}
}

这样,Hook 负责动作发生前的反馈,CI 负责保证 Hook 本身没有悄悄失效。

用加载探针观察规则是否到达

可以对 AGENTS.mdSKILL.md 和 hooks 策略核心计算哈希,把短指纹写回 AGENTS.md,并要求 Agent 在新会话首条回复中回显。

它能帮助发现:

  • 完全没有回显:入口或桥接没有加载;
  • 回显旧指纹:会话缓存、分支或规则副本过期;
  • 指纹正确但 Skill 无标记:Skill 没有触发。

但这个指纹只是加载 canary。它只能说明当前指纹文本进入了上下文,不能证明全文被完整理解, 也不能证明 Agent 会遵守。最终保证仍来自权限系统、Hooks、CI 与人工审查。

落地检查清单

  • AGENTS.md 只保留全局项目边界、命令和完成定义;
  • 领域流程拆进 .agents/skills/<name>/SKILL.md
  • Claude Code 通过 CLAUDE.md 和逐技能链接接入共享内容;
  • 实时策略只写在 .agents/hooks/core/
  • 三端配置只引用 adapters,不复制策略;
  • 用三端真实 payload 测试 adapters;
  • Hooks 检查进入统一 CI 命令;
  • Codex 用户通过 /hooks 审查项目信任;
  • 三端分别新开会话,验证规则、Skill 与 Hook 的实际触发;
  • 每隔数周重新核对产品支持矩阵。

多 Agent 配置真正困难的不是“写规则”,而是建立一条可追踪的传递链:

一个事实源
  → 各端桥接
  → 加载探针
  → 实时 Hooks
  → CI 最终门禁

只有这条链路可观察、可测试、可复核,SSOT 才不是一句口号。

分享到