claude-mem × Cursor Hooks 实战:完整解析 Cursor 钩子机制及其在持久化记忆中的应用
2026/9/7 5:53:21 网站建设 项目流程

claude-mem × Cursor Hooks 实战:完整解析 Cursor 钩子机制及其在持久化记忆中的应用

【免费下载链接】claude-memPersistent Context Across Sessions for Every Agent – Captures everything your agent does during sessions, compresses it with AI, and injects relevant context back into future sessions. Works with Claude Code, OpenClaw, Codex, Gemini, Hermes, Copilot, OpenCode + More项目地址: https://gitcode.com/GitHub_Trending/cl/claude-mem

Cursor Hooks 是 Cursor 提供的钩子机制,允许你通过自定义脚本观察、控制并扩展 Agent 执行循环。本文以 Cursor 官方钩子规范为骨架,系统讲解钩子的事件模型、hooks.json配置体系、各事件的输入/输出 JSON 契约,以及团队分发与排障方法;并结合 claude-mem 仓库中真实的钩子配置与适配层源码,展示如何基于这套机制为 Cursor 构建"跨会话持久记忆",让 Agent 在每次会话中自动捕获操作、生成摘要并注入历史上下文。

一、Hooks 是什么:基于 stdio 的进程级拦截

Hooks 的本质是被 Cursor 派生(spawn)出来的进程,它们通过 stdio 与 Cursor 双向通信,且两个方向都使用 JSON 编码。钩子运行在 Agent 循环的特定阶段之前或之后(before/after),能够完成三类动作:

  • 观察(observe):记录事件、做审计与分析;
  • 阻断(block):拒绝某条 shell 命令、某个 MCP 工具调用或某次 prompt 提交;
  • 修改(modify):向 Agent 注入额外指令消息,或自动提交后续消息。

典型的实际用途包括:

  • 在文件编辑后自动运行格式化工具;
  • 为各类事件补充 analytics 埋点;
  • 扫描 PII(个人身份信息)或密钥泄露;
  • 为高危操作(例如 SQL 写操作)设置准入闸门。

从源码结构看,claude-mem 的钩子入口 hook-command.ts 正是按"stdio 进程 + JSON"这一契约实现的:hookCommand()先用readJsonFromStdin()读取 Cursor 传入的 JSON,再交给平台适配器归一化,最后执行对应事件处理器。这也是理解后文所有事件契约的基础。

二、Agent 钩子与 Tab 钩子的区分

Cursor Hooks 同时服务于两种 AI 能力,但使用不同的事件集合

Cursor Agent(Cmd+K / Agent Chat)使用标准钩子:

事件用途
beforeShellExecution/afterShellExecution控制与审计 shell 命令
beforeMCPExecution/afterMCPExecution控制与审计 MCP 工具调用
beforeReadFile/afterFileEdit控制文件读取与编辑
beforeSubmitPrompt在 prompt 提交前做校验
stop处理 Agent 循环结束
afterAgentResponse/afterAgentThought跟踪 Agent 的响应与思考过程

Cursor Tab(内联补全)使用专用钩子:

  • beforeTabFileRead:控制 Tab 补全读取文件时的访问策略;
  • afterTabFileEdit:对 Tab 产生的编辑做后处理。

这种事件隔离的设计意图是:允许对"Tab 的自主操作"与"用户主导的 Agent 操作"施加不同的安全策略。例如你可以禁止 Tab 读取含密钥的文件,同时保留 Agent 的读取能力(通过审批)。

三、Quickstart:5 分钟跑通第一个钩子

1. 创建hooks.json

钩子配置文件可以放在项目级<project>/.cursor/hooks.json,仅对该项目生效)或用户主目录~/.cursor/hooks.json,全局生效):

{ "version": 1, "hooks": { "afterFileEdit": [{ "command": "./hooks/format.sh" }] } }

2. 编写钩子脚本

创建~/.cursor/hooks/format.sh

#!/bin/bash # 读取输入,做事,退出码 0 cat > /dev/null exit 0

3. 赋予执行权限并重启

chmod +x ~/.cursor/hooks/format.sh

然后重启 Cursor。此后每次文件编辑都会触发这个钩子。

需要注意的隐含契约:

  • command路径若为相对路径,相对于hooks.json文件所在目录解析
  • 钩子必须显式exit 0表示放行/成功,异常退出或输出非预期 JSON 会被记录到 Hooks 输出通道。

四、完整示例:审计日志 + 命令拦截(allow / deny / ask)

下面这组配置把审计脚本挂到几乎所有事件上,并在beforeShellExecution上加了一个策略脚本,把裸git命令改为拒绝、gh命令改为询问:

{ "version": 1, "hooks": { "beforeShellExecution": [ { "command": "./hooks/audit.sh" }, { "command": "./hooks/block-git.sh" } ], "beforeMCPExecution": [ { "command": "./hooks/audit.sh" } ], "afterShellExecution": [ { "command": "./hooks/audit.sh" } ], "afterMCPExecution": [ { "command": "./hooks/audit.sh" } ], "afterFileEdit": [ { "command": "./hooks/audit.sh" } ], "beforeSubmitPrompt": [ { "command": "./hooks/audit.sh" } ], "stop": [ { "command": "./hooks/audit.sh" } ], "beforeTabFileRead": [ { "command": "./hooks/redact-secrets-tab.sh" } ], "afterTabFileEdit": [ { "command": "./hooks/format-tab.sh" } ] } }

审计脚本audit.sh:把钩子收到的全部 JSON 输入带时间戳追加写入审计日志:

#!/bin/bash # audit.sh - 将所有 JSON 输入写入 /tmp/agent-audit.log # 从 stdin 读取 JSON 输入 json_input=$(cat) # 生成日志时间戳 timestamp=$(date '+%Y-%m-%d %H:%M:%S') # 确保日志目录存在 mkdir -p "$(dirname /tmp/agent-audit.log)" # 写入带时间戳的 JSON 条目 echo "[$timestamp] $json_input" >> /tmp/agent-audit.log exit 0

拦截脚本block-git.sh:演示了beforeShellExecution的三种权限决策。关键点在于它用jq从 stdin 的 JSON 里解析command字段,然后按正则匹配输出不同的决策 JSON

#!/bin/bash # 解析命令 command=$(echo "$input" | jq -r '.command // empty') # 裸 git 命令 -> deny(拒绝并给出改写建议) if [[ "$command" =~ git[[:space:]] ]] || [[ "$command" == "git" ]]; then cat << EOF { "continue": true, "permission": "deny", "user_message": "Git command blocked. Please use the GitHub CLI (gh) tool instead.", "agent_message": "The git command '$command' has been blocked by a hook. ..." } EOF # gh 命令 -> ask(弹窗请求用户批准) elif [[ "$command" =~ gh[[:space:]] ]] || [[ "$command" == "gh" ]]; then cat << EOF { "continue": true, "permission": "ask", "user_message": "GitHub CLI command requires permission: $command", "agent_message": "..." } EOF # 其他命令 -> allow(放行) else cat << EOF { "continue": true, "permission": "allow" } EOF fi

这里体现出了钩子输出的两个层次:user_message展示给客户端用户看,agent_message则直接发给模型,引导它改变行为(例如改用gh工具)。这是"修改行为"而非简单"阻断"的经典用法。

生态参考:官方文档列出了多家合作伙伴基于 Hooks 构建的集成,覆盖 MCP 治理与可见性(MintMCP、Oasis Security、Runlayer)、代码安全与最佳实践(Corridor、Semgrep)、依赖供应链安全(Endor Labs)、Agent 安全防护(Snyk Evo Agent Guard)以及密钥管理(1Password Environments 的即时挂载校验)等方向,可以作为设计企业级钩子策略时的参考清单。

五、配置体系:三级配置与优先级

钩子配置存在于多个层级,高优先级来源覆盖低优先级来源

~/.cursor/ ├── hooks.json └── hooks/ ├── audit.sh └── block-git.sh
层级路径说明
Global(企业管理)macOS:/Library/Application Support/Cursor/hooks.json;Linux/WSL:/etc/cursor/hooks.json;Windows:C:\ProgramData\Cursor\hooks.json企业 MDM 统一下发
Project Directory(项目级)<project-root>/.cursor/hooks.json随版本库提交,在任何"受信任工作区"中运行
Home Directory(用户级)~/.cursor/hooks.json对当前用户全局生效

优先级顺序(从高到低):Enterprise → Project → User。

hooks对象将钩子名称映射到钩子定义数组;每个定义目前支持command属性,其值可以是 shell 字符串、绝对路径,或相对于hooks.json文件的路径。一个覆盖常用事件的完整配置示例:

{ "version": 1, "hooks": { "beforeShellExecution": [{ "command": "./script.sh" }], "afterShellExecution": [{ "command": "./script.sh" }], "afterMCPExecution": [{ "command": "./script.sh" }], "afterFileEdit": [{ "command": "./format.sh" }], "beforeTabFileRead": [{ "command": "./redact-secrets-tab.sh" }], "afterTabFileEdit": [{ "command": "./format-tab.sh" }] } }

其中 Agent 钩子(beforeShellExecutionafterShellExecutionbeforeMCPExecutionafterMCPExecutionbeforeReadFileafterFileEditbeforeSubmitPromptstopafterAgentResponseafterAgentThought)作用于 Cmd+K 与 Agent Chat;Tab 钩子(beforeTabFileReadafterTabFileEdit)仅作用于内联补全。

六、团队分发:版本库、MDM 与云端下发

1. 项目钩子(版本控制)——最简单的方式:把hooks.json放到<project-root>/.cursor/hooks.json并提交。团队成员在受信任工作区打开项目后,Cursor 会自动加载运行。项目钩子与代码共存于版本库、可按项目定制策略(如强制某代码库的格式标准),且要求工作区处于受信任状态才可运行。

2. MDM 分发——通过移动设备管理工具把hooks.json与钩子脚本放到目标机器的约定目录:

  • 用户级:~/.cursor/hooks.json~/.cursor/hooks/(脚本目录);
  • 全局级:即上文 Global 三平台的系统目录。

注意:MDM 分发完全由组织自行管理,Cursor 不会通过 MDM 部署或管理文件,需由内部 IT/安全团队按组织策略完成配置、部署与更新。

3. 云端分发(仅企业版)——企业团队可在 Cursor 网页 Dashboard 的 Hooks 配置区统一定义钩子,登录客户端后自动下发,特性包括:每 30 分钟一次的自动同步、按操作系统定向(platform-specific hooks)、Dashboard 集中管理。企业管理员无需接触任何单台机器即可增删改团队钩子。

七、Reference:公共输入 Schema 与全部事件契约

7.1 所有钩子的公共字段

所有钩子在各自专属字段之外,还会收到以下基础字段:

{ "conversation_id": "string", "generation_id": "string", "model": "string", "hook_event_name": "string", "cursor_version": "string", "workspace_roots": ["<path>"], "user_email": "string | null" }
字段类型说明
conversation_idstring跨多轮对话保持稳定的会话 ID
generation_idstring随每条用户消息变化的当前 generation
modelstring触发钩子的 composer 所配置的模型
hook_event_namestring当前正在执行的钩子事件名
cursor_versionstringCursor 应用版本(如 "1.7.2")
workspace_rootsstring[]工作区根目录列表(多根工作区可含多个)
user_emailstring | null已认证用户的邮箱,若可用

7.2beforeShellExecution/beforeMCPExecution

在任何 shell 命令或 MCP 工具执行之前调用,要求返回权限决策:

// beforeShellExecution 输入 { "command": "<完整终端命令>", "cwd": "<当前工作目录>" } // beforeMCPExecution 输入 { "tool_name": "<工具名>", "tool_input": "<JSON 参数>" } // 外加二选一: { "url": "<server url>" } // 远程 MCP server // 或 { "command": "<command string>" } // 本地 MCP server // 输出 { "permission": "allow" | "deny" | "ask", "user_message": "<展示在客户端的消息>", "agent_message": "<发送给 Agent 的消息>" }

7.3afterShellExecution

shell 命令执行之后触发,适合审计或从命令输出中收集指标:

{ "command": "<完整终端命令>", "output": "<完整终端输出>", "duration": 1234 }
字段类型说明
commandstring执行的完整终端命令
outputstring捕获的完整终端输出
durationnumber执行耗时(毫秒),不含等待批准的时间

7.4afterMCPExecution

MCP 工具执行之后触发,包含工具输入参数与完整 JSON 结果:

{ "tool_name": "<工具名>", "tool_input": "<JSON 参数>", "result_json": "<工具结果 JSON>", "duration": 1234 }
字段类型说明
tool_namestring被执行的 MCP 工具名
tool_inputstring传给工具的 JSON 参数字符串
result_jsonstring工具响应的 JSON 字符串
durationnumber执行耗时(毫秒),不含等待批准的时间

7.5afterFileEdit

Agent 编辑文件之后触发,适合格式化器或统计 Agent 产出的代码:

{ "file_path": "<绝对路径>", "edits": [{ "old_string": "<查找串>", "new_string": "<替换串>" }] }

7.6beforeTabFileRead

Tab(内联补全)读取文件之前调用,可在 Tab 接触文件内容前做脱敏或访问控制。与beforeReadFile的关键差异:

  • 仅由 Tab 触发,Agent 不触发;
  • 输入不含attachments字段(Tab 不使用 prompt 附件);
  • 便于对 Tab 的自主操作施加独立策略。
// 输入 { "file_path": "<绝对路径>", "content": "<文件内容>" } // 输出 { "permission": "allow" | "deny" }

7.7afterTabFileEdit

Tab 编辑文件之后调用,适合格式化或审计 Tab 写入的代码。与afterFileEdit的关键差异:

  • 仅由 Tab 触发;
  • 编辑明细更细:edits中每项额外包含range(行列定位)、old_linenew_line,支持精确的编辑追踪;
  • 当前不支持任何输出字段。
{ "file_path": "<绝对路径>", "edits": [ { "old_string": "<查找串>", "new_string": "<替换串>", "range": { "start_line_number": 10, "start_column": 5, "end_line_number": 10, "end_column": 20 }, "old_line": "<编辑前行>", "new_line": "<编辑后行>" } ] }

7.8beforeSubmitPrompt

在用户点击发送之后、后端请求发出之前调用,可以阻止提交

// 输入 { "prompt": "<用户 prompt 文本>", "attachments": [ { "type": "file" | "rule", "filePath": "<绝对路径>" } ] } // 输出 { "continue": true | false, "user_message": "<被阻止时展示给用户的消息>" }
输出字段类型说明
continueboolean是否允许 prompt 提交继续
user_messagestring(可选)prompt 被阻止时展示给用户的消息

7.9afterAgentResponse

Agent 完成一条 assistant 消息后调用,输入为{ "text": "<assistant 最终文本>" }

7.10afterAgentThought

Agent 完成一个 thinking block 后调用,适合观察推理过程。当前不支持输出字段:

{ "text": "<完整聚合的思考文本>", "duration_ms": 5000 }
字段类型说明
textstring已完成思考块的完整聚合文本
duration_msnumber(可选)思考块耗时(毫秒)

7.11stop

Agent 循环结束时调用,可选地自动提交一条后续用户消息,实现"循环直到达成目标"式的流:

// 输入 { "status": "completed" | "aborted" | "error", "loop_count": 0 } // 输出 { "followup_message": "<自动提交的后续消息文本>" }
  • followup_message非空时,Cursor 会自动将其作为下一条用户消息提交;
  • loop_count表示本会话中 stop 钩子已触发自动 follow-up 的次数(从 0 起),为防止死循环,最多允许5 次自动 follow-up。

八、claude-mem 如何用 Hooks 构建跨会话记忆

以上是 Cursor 提供的通用机制;claude-mem 则把它落成了完整的"记忆管道"。仓库内 cursor-hooks/hooks.json 给出了真实的事件映射:

{ "version": 1, "hooks": { "beforeSubmitPrompt": [ { "command": "./cursor-hooks/session-init.sh" }, { "command": "./cursor-hooks/context-inject.sh" } ], "afterMCPExecution": [{ "command": "./cursor-hooks/save-observation.sh" }], "afterShellExecution": [{ "command": "./cursor-hooks/save-observation.sh" }], "afterFileEdit": [{ "command": "./cursor-hooks/save-file-edit.sh" }], "stop": [{ "command": "./cursor-hooks/session-summary.sh" }] } }

其设计思路可以逐事件对应到前文的契约上(详见 cursor-hooks/README.md 与 cursor-hooks/CONTEXT-INJECTION.md):

  1. beforeSubmitPrompt→ 会话初始化 + 上下文就绪session-init.sh用公共字段中的conversation_id作为 claude-mem 的会话 ID 初始化会话(正好利用了"跨多轮稳定的会话 ID"这一契约);context-inject.sh确保 worker 已运行。两者都必须输出{"continue": true}放行提交——这正是beforeSubmitPrompt输出契约的应用。
  2. afterMCPExecution/afterShellExecution→ 观察捕获save-observation.shtool_name/tool_input/result_jsoncommand/output映射为 claude-mem 的 observation 格式,以 fire-and-forget 方式 POST 到 worker 的/api/sessions/observations。观察类钩子不产生决策输出,只负责"读走数据"。
  3. afterFileEdit→ 文件编辑捕获save-file-edit.sheditsold_string/new_string对)视为一次 "write_file" 工具使用并附带编辑摘要。
  4. stop→ 摘要生成 + 上下文回写session-summary.sh请求生成会话摘要,并把最新上下文写入.cursor/rules/claude-mem-context.mdcalwaysApply: true的 Rules 文件),使下一个会话开始时自动携带记忆上下文。上下文实际在三个时机刷新:每次 prompt 前、摘要完成后 worker 自动更新、会话结束兜底。

另外,仓库还提供了一个免脚本维护的分发版本 claude-mem-cursor/hooks/hooks.json,把每条command直接写成npx -y claude-mem hook cursor <event>session-initcontextobservationfile-editsummarize),利用command支持 shell 字符串的特性,让 Cursor 每次派生钩子时由 npx 现取实现,无需在磁盘上维护.sh文件。

8.1 源码视角:Cursor 输入如何被归一化

claude-mem 的 Cursor 适配器 src/cli/adapters/cursor.ts 是上述契约的"消费方",其中几处映射值得注意,恰好印证了 Reference 部分的字段语义:

  • 会话 ID 取值链r.conversation_id || r.generation_id || r.id—— 优先使用跨轮稳定的conversation_id
  • 工作目录r.workspace_roots?.[0] ?? r.cwd ?? process.cwd()—— 兼容多根工作区与beforeShellExecutioncwd字段;
  • shell 与 MCP 的判别!!r.command && !r.tool_name时判定为 shell 命令,归一化为toolName: 'Bash'toolInput: { command }toolResponse: { output };否则按 MCP 处理,响应取自result_json(注释中特别标注"result_json 而非 tool_response",这正是afterMCPExecution的契约字段);
  • stop钩子没有 transcript:由于 Cursor 的 stop 钩子不在 stdin 传 transcript 路径,适配器改为从磁盘推导:Cursor 会把 transcript 以 JSONL 写在~/.cursor/projects/<workspace-slug>/agent-transcripts/<UUID>/<UUID>.jsonlderiveCursorTranscriptPath()(cursor.ts#L22-L28)用workspace_roots[0]生成 slug(去掉前导/,把/.替换为-)再拼出候选路径。这里还加了一道安全边界:sessionId必须匹配^[A-Za-z0-9_-]+$,防止恶意 stdin 中的..或路径分隔符逃逸出~/.cursor/projects目录。

在调度层,src/cli/hook-command.ts 体现了"钩子必须对 Cursor 永远无害"的工程约束:整个 pipeline 的 stdout 只允许输出经平台适配器formatOutput产生的 JSON(Cursor 适配器固定输出{ continue: ... });stderr 被缓冲,成功时静默丢弃、失败时才选择性放出;worker 不可达(连接拒绝/超时/5xx/429)时返回退出码 0 跳过本次钩子,并累计失败计数达到阈值后再向用户"响亮地失败"。这些纪律保证了记忆系统的故障不会反过来阻断 Cursor 的正常操作。

九、Troubleshooting

如何确认钩子已生效:Cursor Settings 中有 Hooks 标签页,可查看已配置与已执行的钩子;另有 Hooks 输出通道(output channel)用于查看错误。

钩子不工作时的检查顺序

  1. 重启 Cursor,确保 hooks 服务在运行;
  2. 相对路径必须相对于hooks.json所在目录解析;
  3. 确认脚本有执行权限:chmod +x ~/.cursor/hooks/*.sh
  4. 确认钩子文件位置正确:ls ~/.cursor/hooks.json(用户级)或ls .cursor/hooks.json(项目级),且项目级要求工作区受信任;
  5. 钩子依赖的基础工具:jq(JSON 解析)、curl(HTTP 请求)、bash

claude-mem 场景的延伸排查(见 cursor-hooks/README.md):

  • worker 是否存活:curl http://127.0.0.1:37777/api/readiness
  • 查看 worker 日志:tail -f ~/.claude-mem/logs/worker-$(date +%Y-%m-%d).log
  • 手动验证 observation 接口:curl -X POST http://127.0.0.1:37777/api/sessions/observations -H "Content-Type: application/json" -d '{"contentSessionId":"test","tool_name":"test","tool_input":{},"tool_response":{},"cwd":"/tmp"}'
  • 若摘要后上下文未更新,检查项目是否已注册进~/.claude-mem/cursor-projects.json——worker 依据该注册表决定是否在摘要生成后自动回写.cursor/rules/claude-mem-context.mdc

十、小结

Cursor Hooks 提供了一套轻量而完整的 Agent 循环拦截协议:hooks.json声明式挂载、stdio 双向 JSON 通信、allow/deny/ask权限决策、continue提交闸门与followup_message循环驱动,覆盖了从 prompt 到循环结束的每个关键节点,并通过 Enterprise → Project → User 的三级配置支持从个人到企业级的策略分发。对 claude-mem 这类跨会话记忆工具而言,beforeSubmitPrompt负责会话建立与上下文就绪、after*系列负责无侵入地捕获操作痕迹、stop负责摘要生成与上下文回写——三者组合即构成"捕获 → 压缩 → 注入"的完整记忆闭环,而适配器层对conversation_idworkspace_rootsresult_json等契约字段的严谨消费,则是钩子数据能够可靠落库的前提。

【免费下载链接】claude-memPersistent Context Across Sessions for Every Agent – Captures everything your agent does during sessions, compresses it with AI, and injects relevant context back into future sessions. Works with Claude Code, OpenClaw, Codex, Gemini, Hermes, Copilot, OpenCode + More项目地址: https://gitcode.com/GitHub_Trending/cl/claude-mem

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询