Cursor 云端 agent hooks 怎么配才拦得住

beforeSubmitPrompt、afterAgentThought 等新 hook 的触发时机、可用字段、自纠正循环写法与超时行为。

Cursor 云端 agent hooks 怎么配才拦得住

云端 agent 跑完给你一个 PR,中间那几十分钟是黑的。cursor 云端 agent hooks 怎么配,决定了你能不能把提示词、思考块、子 agent 调度都落成日志。

作者CodePass 技术编辑

这批 hook 观测的是对话本身

3.11 那条 changelog 讲得很准确:云端 agent 原本已支持围绕工具执行和文件、shell 操作的团队 hook,这次加的是观测并控制 agent 对话本身的能力,覆盖提示词、回复、思考、子 agent、compaction 和一轮对话的结束。旧的那批看得见「它执行了什么命令」,新的这批看得见「它当时在想什么、准备回什么、要派谁去做」。

差别体现在排查场景上。一个云端 agent 改错了文件,只有 beforeShellExecution 的日志时,你看到的是一串命令,推不出它为什么选了这条路。有 afterAgentThought 的记录,思考块原文就在 JSONL 里,配上 afterAgentResponsesubagentStart 的派发记录,一轮跑完能还原出完整决策序列。

每个事件卡在流程的哪一格

{
  "version": 1,
  "hooks": {
    "beforeSubmitPrompt": [{ "command": ".cursor/hooks/guard-prompt.sh" }],
    "afterAgentThought": [{ "command": ".cursor/hooks/log-thought.sh" }],
    "afterAgentResponse": [{ "command": ".cursor/hooks/log-response.sh" }],
    "subagentStart": [{ "command": ".cursor/hooks/gate-subagent.sh", "matcher": "explore|shell" }],
    "stop": [{ "command": ".cursor/hooks/on-stop.mjs", "loop_limit": 3 }]
  }
}

文件放在仓库根目录的 .cursor/hooks.json,云端 agent 工作时会自己捡起来。脚本路径相对项目根目录写,写成 ./hooks/xxx.sh 会找不到,文档专门提醒过。

各事件的触发点分得很细。beforeSubmitPrompt 在用户点发送之后、请求发往后端之前;afterAgentThought 在一个思考块聚合完成后;afterAgentResponse 在一条助手消息完成后;subagentStart 在 Task 工具派生子 agent 之前,subagentStop 在子 agent 完成、报错或被中止时;preCompact 在上下文压缩发生前;stop 在整个 agent 循环结束时。matcher 的匹配对象各不相同,subagentStart 匹配子 agent 类型(generalPurposeexploreshell),beforeSubmitPrompt 匹配固定值 UserPromptSubmit,另两个分别匹配 AgentResponseAgentThought

每个事件拿得到什么、能拦什么

所有 hook 都会先收到一组公共字段:conversation_id 跨多轮稳定,generation_id 每条用户消息变一次,还有 modelmodel_idmodel_paramshook_event_namecursor_versionworkspace_rootsuser_emailtranscript_path。用 conversation_id 做主键把各事件串起来,就是一条可回放的时间线。

事件 输入关键字段 能否拦截
beforeSubmitPrompt promptattachments[] 返回 continue: false 阻止提交
afterAgentThought textduration_ms 纯观测,无输出字段
afterAgentResponse text 纯观测,无输出字段
subagentStart subagent_typetasksubagent_modelis_parallel_workergit_branch 返回 permission: "deny" 阻止派生
preCompact context_usage_percentmessages_to_compactis_first_compaction 观测型,只能返回 user_message
stop statusloop_count 可返回 followup_message 续跑

有两处语义容易踩错。subagentStartpermission 不接受 "ask",文档写明这个值会被当成 deny 处理,写成 ask 等于把子 agent 全掐了。preCompact 则无法阻止或修改压缩行为,只能记录压缩何时发生。信息量最大的是 subagentStop,除了 statussummary,还带 duration_msmessage_counttool_call_countmodified_filesagent_transcript_path,做成本核算和子任务质量统计够用。

云端跑和本地跑不一样的地方

文档列的云端支持清单有 14 项,包含上面这几个新增的。不支持的是 sessionStartsessionEndbeforeMCPExecutionafterMCPExecution、两个 Tab hook 和 workspaceOpen。理由写得很具体:云端 agent 有时以只读环境开始跑前几轮探索,那段时间 hook 根本不加载,云端的 sessionStart 会晚到第一次写入之后才触发。

这条限制的副作用是:只读阶段的那几轮完全没有 hook 记录。审计要求「每一步都有痕迹」的话,这段空窗要在设计时就承认。

配置来源也窄了一圈。云端只读三处:仓库里的 .cursor/hooks.json、企业版的 team hooks 和 enterprise hooks。~/.cursor/hooks.json 不生效,云端 VM 访问不到你本机的家目录。执行类型上云端只跑 command 类型,prompt 类型(交给一个快模型做自然语言判断)因为鉴权链路没打通而不可用,本地写惯了 prompt hook 的人搬上云会直接失效。

还有一处官方材料互相打架。docs 的云端支持表把 beforeSubmitPrompt 标为 Yes,而 cursor/cookbook 仓库的 hooks/README 在 Notes 里写着它对云端 agent 不可用,理由是提示词在云端 VM 存在之前就已提交。哪一边反映当前实现需要自行验证,稳妥做法是把提示词侧的拦截同时在本地做一份。策略怎么跨客户端保持一致,策略在 Claude Code 与 Cursor 之间怎么统一执行 里讨论过。

用 stop 搭一个自纠正循环

stop 的输出里有个 followup_message,返回非空字符串时 Cursor 会把它当成下一条用户消息自动提交,循环就这么搭起来。典型写法是在 stop 脚本里跑一遍验收命令,测试挂了就把失败摘要塞进 followup_message,让 agent 再来一轮。

防跑飞的闸门是 loop_countloop_limit。前者告诉你这个会话已经被自动续过几次,从 0 开始;后者是每个脚本的上限,默认 5,设成 null 取消限制。文档示例的逻辑是错误连续两次且 loop_count < 4 才重试,这个双重条件值得照抄。subagentStop 也支持 followup_message,但只在 statuscompleted 时消费,且共用同一套 loop 限制。

云端跑循环还有一笔账:每一轮续跑都是真实的 token 和机器时间。环境配得不干净时,循环放大的是失败而不是修复,这方面 云端 agent 环境配置踩过的坑 里的教训值得先过一遍。

脚本崩了、超时了会怎样

默认是 fail-open。文档写的退出码语义是:0 表示成功并采用 stdout 里的 JSON,2 表示阻断(等价于返回 permission: "deny"),其他退出码一律视为 hook 失败,动作照常放行。也就是说一个写错了的守卫脚本不会挡住任何东西,它只会静静地失败。

想要反过来,就在 hook 定义上加 failClosed: true,崩溃、超时、返回非法 JSON 都会阻断动作,文档对 beforeMCPExecutionbeforeReadFile 这类安全敏感路径明确推荐它。cookbook 里 sensitive-prompt-guard.sh 的示例配置就带着 failClosed: true,同时脚本在仓库没有 git origin 时主动返回 continue: true,这种整体从严、已知无害情形放行的写法比一刀切更耐用。

超时用 timeout 配,单位是秒,不填走平台默认值,涉及网络调用的 hook 尤其容易在这里翻车。排查手段上,本地有 Customize 里的 Hooks 标签页和 Hooks 输出通道,云端没有对应界面,只能靠脚本自己往文件或外部服务写日志。hook 配了却一次都不触发时的排查顺序,hooks 不触发怎么查 里那套方法同样适用。

把对话送进脚本之后的安全边界

这批 hook 的输入里装着提示词原文、思考块全文和助手回复全文。把这些内容 POST 到一个外部 endpoint,等于在 Cursor 的数据流之外又开了一条出口,走审批时要单独说明。cookbook 的 README 也提醒了一句:日志类 hook 会捕获敏感信息,在真实仓库里启用前先看清楚它记了什么。

密钥不要写进 hooks.json。这个文件按设计就是要提交进版本库的,团队成员和云端 VM 都会读到它。需要凭证时走环境变量,云端可以用创建 agent 时的 envVars,文档说明这些值加密存储、注入到 agent 的 shell、随 agent 删除,名字不能以 CURSOR_ 开头。

还有两个细节值得单独盯。transcript_path 和环境变量 CURSOR_TRANSCRIPT_PATH 指向完整会话记录,脚本能读到的远不止当前事件那点内容,把整个文件外传是很容易顺手写出来的错误。配置优先级是 Enterprise → Team → Project → User,项目级 hook 压不过企业和团队级,安全团队要收紧就往上面两层放,而不是指望每个仓库自觉。

参考资料