Cursor 云端 agent hooks 怎么配才拦得住
beforeSubmitPrompt、afterAgentThought 等新 hook 的触发时机、可用字段、自纠正循环写法与超时行为。

云端 agent 跑完给你一个 PR,中间那几十分钟是黑的。cursor 云端 agent hooks 怎么配,决定了你能不能把提示词、思考块、子 agent 调度都落成日志。
这批 hook 观测的是对话本身
3.11 那条 changelog 讲得很准确:云端 agent 原本已支持围绕工具执行和文件、shell 操作的团队 hook,这次加的是观测并控制 agent 对话本身的能力,覆盖提示词、回复、思考、子 agent、compaction 和一轮对话的结束。旧的那批看得见「它执行了什么命令」,新的这批看得见「它当时在想什么、准备回什么、要派谁去做」。
差别体现在排查场景上。一个云端 agent 改错了文件,只有 beforeShellExecution 的日志时,你看到的是一串命令,推不出它为什么选了这条路。有 afterAgentThought 的记录,思考块原文就在 JSONL 里,配上 afterAgentResponse 和 subagentStart 的派发记录,一轮跑完能还原出完整决策序列。
每个事件卡在流程的哪一格
{
"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 类型(generalPurpose、explore、shell),beforeSubmitPrompt 匹配固定值 UserPromptSubmit,另两个分别匹配 AgentResponse 和 AgentThought。
每个事件拿得到什么、能拦什么
所有 hook 都会先收到一组公共字段:conversation_id 跨多轮稳定,generation_id 每条用户消息变一次,还有 model、model_id、model_params、hook_event_name、cursor_version、workspace_roots、user_email、transcript_path。用 conversation_id 做主键把各事件串起来,就是一条可回放的时间线。
| 事件 | 输入关键字段 | 能否拦截 |
|---|---|---|
beforeSubmitPrompt |
prompt、attachments[] |
返回 continue: false 阻止提交 |
afterAgentThought |
text、duration_ms |
纯观测,无输出字段 |
afterAgentResponse |
text |
纯观测,无输出字段 |
subagentStart |
subagent_type、task、subagent_model、is_parallel_worker、git_branch |
返回 permission: "deny" 阻止派生 |
preCompact |
context_usage_percent、messages_to_compact、is_first_compaction |
观测型,只能返回 user_message |
stop |
status、loop_count |
可返回 followup_message 续跑 |
有两处语义容易踩错。subagentStart 的 permission 不接受 "ask",文档写明这个值会被当成 deny 处理,写成 ask 等于把子 agent 全掐了。preCompact 则无法阻止或修改压缩行为,只能记录压缩何时发生。信息量最大的是 subagentStop,除了 status 和 summary,还带 duration_ms、message_count、tool_call_count、modified_files 和 agent_transcript_path,做成本核算和子任务质量统计够用。
云端跑和本地跑不一样的地方
文档列的云端支持清单有 14 项,包含上面这几个新增的。不支持的是 sessionStart、sessionEnd、beforeMCPExecution、afterMCPExecution、两个 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_count 和 loop_limit。前者告诉你这个会话已经被自动续过几次,从 0 开始;后者是每个脚本的上限,默认 5,设成 null 取消限制。文档示例的逻辑是错误连续两次且 loop_count < 4 才重试,这个双重条件值得照抄。subagentStop 也支持 followup_message,但只在 status 为 completed 时消费,且共用同一套 loop 限制。
云端跑循环还有一笔账:每一轮续跑都是真实的 token 和机器时间。环境配得不干净时,循环放大的是失败而不是修复,这方面 云端 agent 环境配置踩过的坑 里的教训值得先过一遍。
脚本崩了、超时了会怎样
默认是 fail-open。文档写的退出码语义是:0 表示成功并采用 stdout 里的 JSON,2 表示阻断(等价于返回 permission: "deny"),其他退出码一律视为 hook 失败,动作照常放行。也就是说一个写错了的守卫脚本不会挡住任何东西,它只会静静地失败。
想要反过来,就在 hook 定义上加 failClosed: true,崩溃、超时、返回非法 JSON 都会阻断动作,文档对 beforeMCPExecution、beforeReadFile 这类安全敏感路径明确推荐它。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 压不过企业和团队级,安全团队要收紧就往上面两层放,而不是指望每个仓库自觉。
参考资料
- Cursor Docs: Hooks — 事件清单、输入输出 schema、云端支持表、退出码与 failClosed 语义
- Side Chats and Conversation Search — 3.11 新增云端对话层 hook 的原始表述与事件名
- cursor/cookbook hooks/README.md — 官方示例脚本、failClosed 用法,以及云端 beforeSubmitPrompt 的那条说明
- Cloud Agents API —
envVars的加密存储、注入方式与命名限制