Cursor Hooks 怎么写:能挂的时点与能改的东西
如果你想让 Agent 改完文件自动跑一遍格式化,或者想拦下某几类 shell 命令,翻文档的时候最容易卡住的不是”怎么写脚本”,而是”到底有哪些时点可以挂,挂上去之后我能拿到什么、能改什么”。这两件事没数清楚之前,写出来的脚本经常挂错位置——比如你想拦 MCP 调用,结果挂在了一个只能观察不能拦的事件点上。
这篇就把 Cursor 官方文档 cursor.com/docs/hooks 那一页里写明的事件点清单数一遍,顺带说清楚每个点的输入输出约定。
一、hooks 解决的是”在 Agent 循环里插一脚”
官方文档对 hooks 的定义是:用自定义脚本来观察、控制和扩展 agent loop。hooks 是被派生出来的进程,双向用 JSON 通过 stdio 通信,在 agent loop 的既定阶段之前或之后运行,可以观察、阻断或修改行为。
文档列出的典型用途包括:编辑后跑格式化工具、给事件加埋点、扫描 PII 或密钥、给高风险操作加闸(文档举的例子是 SQL 写入)、控制 subagent(也就是 Task 工具)的执行、在会话开始时注入上下文。
重点是”被派生出来的进程”这句:你的脚本是独立进程,读 stdin 拿 JSON,往 stdout 吐 JSON。这决定了你用什么语言写都行——官方文档的示例里就同时给了 Bash、TypeScript(用 Bun 跑)和 Python 三种。
二、前置条件:配置放在哪一层,谁的优先级高
hooks 定义在 hooks.json 文件里。文档写明配置可以存在于多个层级,所有匹配到的 hooks 都会从每个来源运行;当返回结果冲突时,合并时由高优先级来源胜出。
四个层级依次是:
| 层级 | 位置 | 备注 |
|---|---|---|
| Enterprise(MDM 管理,系统级) | macOS:/Library/Application Support/Cursor/hooks.json;Linux/WSL:/etc/cursor/hooks.json;Windows:C:\ProgramData\Cursor\hooks.json | 由你所在组织的 IT 部署 |
| Team(云分发,仅 Enterprise) | 在 web dashboard 配置,自动同步到团队成员 | 文档写明会按固定周期自动同步,具体周期以官方文档为准 |
| Project(项目级) | <project-root>/.cursor/hooks.json | 随代码进版本控制,要求工作区是受信任的(trusted workspace)才会运行 |
| User(用户级) | ~/.cursor/hooks.json | — |
优先级从高到低:Enterprise → Team → Project → User。
这里有个非常容易踩的坑:脚本的工作目录取决于 hook 来自哪一层。文档写得很明确——project hooks 从项目根目录运行,user hooks 从 ~/.cursor/ 运行,enterprise hooks 从企业配置目录运行,team hooks 从托管 hooks 目录运行。所以同一个脚本路径,写在项目配置里要用 .cursor/hooks/script.sh,写在用户配置里则是 ./hooks/script.sh;写反了就是找不到文件。文档专门用一段话提醒了这一点。
Windows 侧要注意的是:官方文档在 Windows 上只写明了企业级配置的绝对路径 C:\ProgramData\Cursor\hooks.json;示例脚本一律是 .sh 并配 chmod +x,这是 Linux/macOS 的做法。Windows 上 .sh 脚本该由什么解释器执行、chmod +x 这一步该如何替代,官方文档没有说明这一点。 一个可行的绕法是照抄文档 TypeScript / Python 示例的写法——command 直接写成 bun run .cursor/hooks/track-stop.ts --stop 或 python3 .cursor/hooks/kube_guard.py(这两条是文档示例里的原样写法),把解释器显式写进命令里,因为文档写明 command 可以是 shell 字符串、绝对路径或相对路径。另外用 WSL 的读者注意,企业级路径那一栏里 Linux 和 WSL 是并列写在一起的。
另外,Cursor 支持加载来自 Claude Code 等第三方工具的 hooks,兼容性与配置细节在 cursor.com/docs/reference/third-party-hooks 单独一页,本文不展开。
三、事件点清单:三类,一共二十一个
文档按”什么东西触发它”把 hooks 分成三类。这是本篇的落点,逐个数:
Agent hooks(Cmd+K / Agent Chat),在 agent 会话期间触发,文档列出十八个:sessionStart、sessionEnd、preToolUse、postToolUse、postToolUseFailure、subagentStart、subagentStop、beforeShellExecution、afterShellExecution、beforeMCPExecution、afterMCPExecution、beforeReadFile、afterFileEdit、beforeSubmitPrompt、preCompact、stop、afterAgentResponse、afterAgentThought。
Tab hooks(内联补全),为自主的 Tab 操作触发,两个:beforeTabFileRead、afterTabFileEdit。
App lifecycle hooks,在任何 agent 会话之外触发,一个:workspaceOpen——Cursor 打开工作区时以及每次工作区文件夹变更时触发,可以返回要为当前工作区加载的额外 plugin 路径。
合计二十一个。文档自述这样分面的目的,是”让你能对自主的 Tab 操作、用户驱动的 Agent 操作和工作区启动应用不同的策略”。
注意 preToolUse / postToolUse / postToolUseFailure 是通用工具钩子,对所有工具都会触发,要靠 matcher 过滤;而 beforeShellExecution、beforeMCPExecution、beforeReadFile、afterFileEdit 是按操作类型分好的专用钩子。同一次 shell 执行会不会同时命中通用钩子和专用钩子,官方文档没有说明这一点。
能改回去什么,按事件点分
只能观察的、能拦的、能改内容的,得分清楚:
preToolUse:输出permission("allow"/"deny")、user_message、agent_message,以及updated_input——文档逐个事件点列出的输出字段里,只有这里给了替换工具入参的字段,文档示例是把npm install换成npm ci。postToolUse:输出updated_mcp_tool_output(文档注明”仅 MCP 工具”,替换模型看到的工具输出)和additional_context(在工具结果之后往对话里注入额外上下文)。beforeShellExecution/beforeMCPExecution:输出permission,取值是"allow"/"deny"/"ask"三种。beforeReadFile/beforeTabFileRead:输出permission的"allow"/"deny",用来挡住敏感文件进模型。beforeSubmitPrompt:输出continue(布尔)决定这次提交放不放行。stop/subagentStop:输出followup_message,非空时 Cursor 会自动把它作为下一条用户消息提交,用来做循环式流程。subagentStop的这个字段文档写明只在status为"completed"时被消费。sessionStart:输出env(本会话的环境变量,文档写明会传给该会话内后续所有 hook 执行)和additional_context。workspaceOpen:输出pluginPaths。
所有 hook 都会收到一组公共输入字段,包括 conversation_id、generation_id、hook_event_name、cursor_version、workspace_roots、user_email、transcript_path 等。文档特别标注:workspaceOpen 因为在会话之外触发,请求里不带 conversation_id、generation_id、model、session_id 和 transcript_path。
四、边界:文档明说不生效的几处
这一节是我觉得最该先读的,因为它们全是”schema 收了但行为不是你以为的那样”。
"ask" 不是到处都能用。 文档在 preToolUse 的输出表里写明:"ask" 被 schema 接受,但目前对 preToolUse 不强制执行。subagentStart 更直接——"ask" 不受支持,会被当作 "deny" 处理。真要走”询问”这条路,得挂到 beforeShellExecution / beforeMCPExecution 上。
有些钩子是 fire-and-forget。 sessionStart 文档写明 agent loop 不会等待也不强制阻断式响应;schema 虽然也接受 continue 和 user_message,但当前调用方不强制,即使 continue 为 false 也不会阻止会话创建。sessionEnd 同样是 fire-and-forget,响应会被记录但不被使用。
preCompact 只能看不能拦。 文档明说它是观察性钩子,不能阻断也不能修改压缩行为。
几个事件点当前没有输出字段:postToolUseFailure、afterTabFileEdit、afterAgentThought 的输出示例里写的都是 “No output fields currently supported”。
失败时默认是放行的。 命令型 hook 的退出码约定是:0 表示成功、使用其 JSON 输出;2 表示阻断该动作(等价于返回 permission: "deny");其它退出码表示 hook 失败,动作照常进行——文档原文就是 fail-open by default。想反过来,得在 hook 定义上设 failClosed: true,文档说这对安全关键的 beforeMCPExecution 是推荐做法,beforeReadFile 那一节也单独重复了同样的说明。这一条建议在动手前先想清楚:默认配置下,你的安全钩子崩了等于没装。
云端 Agent 上少一批事件点。 Cloud agent 会跑仓库里 .cursor/hooks.json 的 hooks,但文档给了两张表:能跑的十四个(beforeShellExecution、afterShellExecution、beforeReadFile、afterFileEdit、preToolUse、postToolUse、postToolUseFailure、subagentStart、subagentStop、beforeSubmitPrompt、preCompact、afterAgentResponse、afterAgentThought、stop),跑不了的七个是 sessionStart、sessionEnd、beforeMCPExecution、afterMCPExecution、beforeTabFileRead、afterTabFileEdit、workspaceOpen。十四加七正好是前面数出来的二十一个。
文档给出的理由(属文档自述):sessionStart 与 MCP 两个钩子是deferred(暂缓)状态,因为云端 Agent 仍可能在只读环境里起步,那时 hooks 不加载;sessionEnd 绑定的是 IDE 会话生命周期;Tab 与 workspaceOpen 属于 IDE 特性。还有两条硬约束:云端 Agent 只跑命令型 hooks(prompt 型需要鉴权链路,云端执行环境里没有),以及用户级 ~/.cursor/hooks.json 在云端不可用。另外文档提醒,云端 Agent 有时会以只读环境开始早期探索轮次,那些轮次里 hooks 不运行。
prompt 型 hook 的边界。 除命令型(默认)外还有 prompt 型:用 LLM 评估一句自然语言条件,返回 { ok: boolean, reason?: string } 结构。文档写明 $ARGUMENTS 占位符会被自动替换为 hook 输入 JSON,缺省时输入会被自动追加,还有一个可选的 model 字段可以覆盖默认模型。文档说它”使用一个快速模型”做评估——具体是哪个模型、评估结果的可靠程度如何,官方文档没有说明这一点,别把它当成确定性的策略引擎用。
单脚本可配的选项,文档给的表是:command(必填)、type("command" / "prompt",默认 "command")、timeout(秒,默认为平台默认值)、loop_limit(用于 stop / subagentStop 的每脚本循环上限,null 表示不限;文档写明 Cursor 自家 hooks 与来自 Claude Code 的 hooks 默认值不同)、failClosed(默认 false)、matcher。这些默认值随版本会变,以官方文档最新内容为准。
matcher 匹配的是哪个字段,也随钩子而变,这点反直觉:preToolUse / postToolUse / postToolUseFailure 按工具类型匹配(文档给出的取值包括 Shell、Read、Write、Grep、Delete、Task,MCP 工具用 MCP:<tool_name> 格式);subagentStart / subagentStop 按 subagent 类型匹配(generalPurpose、explore、shell 等);beforeShellExecution / afterShellExecution 匹配的是完整的 shell 命令字符串;beforeSubmitPrompt 匹配固定值 UserPromptSubmit,stop 匹配 Stop,afterAgentResponse 匹配 AgentResponse,afterAgentThought 匹配 AgentThought。
五、怎么确认配对了
官方文档 Quickstart 给的最小可用配置是这样(用户级,~/.cursor/hooks.json):
{
"version": 1,
"hooks": {
"afterFileEdit": [{ "command": "./hooks/format.sh" }]
}
}
项目级则改成 <project>/.cursor/hooks.json,命令路径同步改为 .cursor/hooks/format.sh——文档特意加了一句 Note 强调这个区别。配套脚本文档也给了最小骨架:
#!/bin/bash
# Read input, do something, exit 0
cat > /dev/null
exit 0
在 Linux/macOS 上还要 chmod +x ~/.cursor/hooks/format.sh(项目级则是 chmod +x .cursor/hooks/format.sh)。
验证手段,文档在 Troubleshooting 一节写明了两条:其一,Customize 里有一个 Hooks 标签页,另有一个 Hooks 输出通道,用于调试已配置和已执行的 hooks 并查看报错;其二,Cursor 会监视 hooks.json 文件并在保存时自动重载,如果 hooks 仍未加载,就重启 Cursor。
想自己看到 JSON 长什么样,文档的 audit.sh 示例就是干这个的:读 stdin 的 JSON,加时间戳后追加写进一个日志文件,然后 exit 0。示例里写的路径是 /tmp/agent-audit.log,这是 Linux/macOS 的写法;在 Windows 上要换成一个你自己有写权限的路径(这属于通用做法,不是官方文档内容)。挂上之后触发一次对应操作,看日志里有没有条目、hook_event_name 是不是你以为的那个,比对着文档的字段表核一遍,比猜快得多。
排查路径不对时,文档给的检查点还是那条:project hooks 的相对路径是相对项目根目录,user hooks 的相对路径是相对 ~/.cursor/。
以上配置片段均原样取自官方文档;组合使用时以官方文档与你所在版本的实际行为为准。该产品迭代频繁,文中涉及的设置项、字段名与默认值随版本变动,请以官方文档最新内容为准。
本文依据 Cursor 官方文档(cursor.com/docs 与 cursor.com/help)于 2026-08-18 的公开内容整理。
该产品闭源,本文只复述官方文档写明的机制,不推断其内部实现;
我们没有对文中涉及的功能做过实测,因此不涉及界面外观、操作手感与运行速度的任何描述。
该产品迭代频繁,文中涉及的设置项与命令随版本变动,请以官方文档最新内容为准。
本文不涉及订阅价格、额度与模型清单,相关信息请以官方定价与模型说明页为准。
安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。