1) dsh-server-docs/ 从工作区(原 E:\...\aliyun-dsh-server\dsh-server-docs)**整体并入本仓**,
保留目录名 ⇒ 仓库内 dsh-server-docs/... 的相对引用天然继续有效;旧目录(含其 .git)已归档到
工作区 _中间产物_待清理/,未随本提交带入。
2) .gitattributes:新增 `dsh-server-docs/** -text` —— 原文档库是 `* -text` + autocrlf=false,
必须保持纯 LF,否则会被本仓的 CRLF 规则翻掉。
3) 活引用里的绝对路径已全部改到新位置(docs 的 INDEX / README / scripts / skills + 用户级 skills
+ ~/.workbuddy/settings.json 的 hooks);历史档案(04-调整方案/、archive/)按「只增不改」未动。
⚠️ hooks 路径改动需「完全重启会话」才生效(配置是会话启动快照)。
4) 交接单/T08:新增 §16「生产整体切换执行记录」(形态 / 落地动作 / **4 个只有真上线才暴露的真 bug** /
验收证据 / 回滚命令 / 残留项);台账 T08 行 → 已完成并归档;03-路线图 §二 登记 T08 收尾项。
5) 统一称谓:**「本机」只指跑 WorkBuddy 的开发机**,47 / 106 一律写「远程服务器」。
20 KiB
73 · 让「锁」真正拦得住人:措辞修正 + PreToolUse 强制钩子
- 日期:2026-09-12
- 触发:用户「会话并行修改不是应该有锁的机制吗,知道这个机制吗」→ 复盘发现机制存在,但当天被跳过两次(含我自己的那一次)
- 结论一句话:机制不缺,缺的是强制。根因有两条 —— ① 没有任何强制入口(
settings.json的hooks段实测为null)② guard 的输出语义有歧义:「无全局锁」被读成「环境干净,可以开工」,而正确读法是「你快去抢锁」。本次两条都治:措辞修正(库内)+ PreToolUse 强制钩子(配置层,无锁直接拒写)。 - 状态:✅ 已实现并单点验证;⚠️ 钩子待用户在
/hooks面板审核启用(外部改settings.json需审核才生效) - 关联:档案 69(三把锁的建立)/
交接单/README.md§一 · §三 0·13 · §六 /scripts/handoff-guard.sh·op-lock.sh/scripts/lock-guard-hook.py(本次新增)/决策方法dsh-decision-method§4.4
一、问题:机制有,人没遵守
| 谁 | 情况 |
|---|---|
| 本会话 | 做 T06(实例回收自动恢复)时,只跑了 handoff-guard.sh 的信息模式 —— 看到「(无单级锁)(无全局锁)」→ 读成"环境干净,可以动手",直接开工。正确动作是:没锁 ⇒ 下一步就是抢锁。 |
| 另一个会话 | 改 README.md / DEPLOY-本部署.md / skills/…/SKILL.md 时也没占锁(当时三把锁全空)。 |
定性(按 dsh-decision-method X3 的判据):冲突只认两个硬信号 —— 别人的占用锁 + 待推送清单里的未声明文件。按此复核,双方改的文件不重叠,没有产生实际损害 → 本次属流程失效,不是事故(避免过度反应)。但它确实在同一文件 INDEX.md 上留了残留(对方 1 行改动被我的 commit 收进去)。
二、根因(两条)
- 没有强制入口 —— 三个位置的
settings.json(用户级 / 项目级 / 库内)hooks段全为null。锁完全靠"自觉"。 handoff-guard.sh的输出语义有歧义 —— 「✓ 无全局锁」在视觉上像"检查通过",而它的真实含义是"尚未持锁"。这是本会话踩坑的直接原因,是我自己写的文案没说清。
三、方案对比(按 dsh-decision-method §4.4 裁决顺序)
| 方案 | 内容 | 判定 | 理由 |
|---|---|---|---|
| a. 措辞修正 | guard 无锁时明确输出「⚠ 不是可以开工,是你快去抢」+ 交接单补语义 | ✅ 采用 | 治第 2 条根因;库内改动、零风险 |
| b. 只做 a | —— | ❌ | 约定拦不住不看的人(今天已证) |
| c. PreToolUse 强制钩子 | 写本库/代码库时无锁直接 deny + SessionStart 打印锁状态 | ✅ 采用 | 治第 1 条根因;§4.4 第 3 条「能配置解决就不改码」命中(hooks 是配置层,免 build 免重启) |
| d. 拦 Bash | 连 bash 写文件也拦 |
❌ 不做 | 会把自己锁死(抢锁命令本身要能跑);Bash 写文件是少数派且破坏面可见(git status) |
| e. 靠自觉 | —— | ❌ | 今天已证明不可靠 |
十问自检(关键四条):影响谁 → 仅本库/本代码库(路径前缀匹配,对其他项目零影响)|断多久 → 不中断任何服务|回滚 → 删 settings.json 的 hooks 段或面板关闭|验收 → 四态命令可复现(见 §四)。
四、实现
4.1 措辞修正(库内)
| 文件 | 改动 |
|---|---|
scripts/handoff-guard.sh |
① 【1】单级锁「✓ 无人占用」→ 追加「⚠ 这不是「可以开工」,是「你快去抢」」+ 抢锁命令 ② 【1c】「✓ 无全局锁」→ 改为「⚠️ 这不是「可以开工」,是「你快去抢」」+ 命令 + 一句判据(「环境干净」≠「没人动过」) ③ 信息模式结论行追加同样的提示,并注明 2026-09-12 的实证 |
交接单/README.md §一 |
两级锁小节补一条:「「无锁」的正确读法 =「你快去抢」;看到无锁 ⇒ 下一个动作就是 --claim-exec;抢不到 = 有人在跑 = 停手」 |
4.2 强制钩子(scripts/lock-guard-hook.py,新增)
| 事件 | 行为 |
|---|---|
PreToolUse(matcher Write|Edit) |
目标路径落在受保护根内 且 .exec-lock 不存在 → 输出 permissionDecision: "deny",理由含可直接粘贴的抢锁命令 |
| SessionStart | 打印锁状态:已占用 → 显示占用者;空闲 → 提醒「空闲 ≠ 可以开工,动手前先抢锁」 |
作用域(刻意收窄):只判两类根 —— DSH_DOCS_ROOT(默认 E:\ProgramData\AI技能\aliyun-dsh-server\dsh-server-docs)与 DSH_CODE_REPO(默认 D:\github\dsh_shenxian),可用 env 覆盖。其余路径一律放行 → 对其他项目零影响。
四态单点验证(用临时库模拟,真锁未被触碰):
| # | 场景 | 期望 | 实测 |
|---|---|---|---|
| ① | 受保护路径 + 无锁 | deny | ✅ 返回 deny + 完整理由 + 抢锁命令 |
| ② | 受保护路径 + 有锁 | 放行 | ✅ 空输出 |
| ③ | 非受保护路径 | 放行 | ✅ 空输出 |
| ④ | SessionStart(有锁态) | 提示占用者 | ✅ 「🔐 全局执行锁【已被占用】:tester」 |
异常安全:payload 解析失败 / 脚本内部异常 → 一律放行(except: pass + 退出码恒 0),绝不因为钩子自身问题阻断工作。
4.3 启用方式(需用户操作)
写入 ~/.workbuddy/settings.json 的 hooks 段:
{
"hooks": {
"PreToolUse": [ { "matcher": "Write|Edit", "hooks": [ { "type": "command", "command": "python \"E:/ProgramData/AI技能/aliyun-dsh-server/dsh-server-docs/scripts/lock-guard-hook.py\"", "timeout": 10 } ] } ],
"SessionStart":[ { "matcher": "startup", "hooks": [ { "type": "command", "command": "python \"E:/ProgramData/AI技能/aliyun-dsh-server/dsh-server-docs/scripts/lock-guard-hook.py\"", "timeout": 10 } ] } ]
}
}
⚠️ 外部修改 settings.json 需在 /hooks 面板审核后才生效(本次由我写入配置,启用动作留给用户)。
五、为什么刻意不拦 Bash(重要)
- 抢锁命令本身必须能跑 —— 否则「拿不到锁就不能改文件、不能改文件就抢不了锁」= 死锁;
- Bash 写文件是少数派,且破坏面可见(
git status/ 对账能查出来); - 宁可留一个显式的安全阀(Bash 可绕),也不要一个可能导致死锁的强制层。
六、回滚
- 措辞:
git revert <本次 commit>。 - 钩子:删
settings.json的hooks段(或在/hooks面板关闭);lock-guard-hook.py为独立文件,可留可删。 - 钩子不加任何持久状态,不写锁、不写日志。
七、遗留 / 边界
- 归属判定未做:钩子只判「有没有锁」,不判「锁是不是你的」(hook 拿不到会话名与锁 OWNER 的对应关系)。这已足够 —— 今天的失败模式正是"两边都没抢锁",钩子会两边都拦,而抢锁是原子的 → 串行化达成。
- Bash 是显式缺口(见 §五),不修。
- 钩子只在 WorkBuddy 层生效:若换个工具(或用户手工编辑文件)绕过,仍以约定与台账为准。
- 本次未改动
04-调整方案/既有档案(遵守库内新约定「档案只增不改」);与此前档案冲突处一律以本档 + 入口文件为准。
八、补:让规则真正到达每个会话(用户追问后)
用户追问:「关键是别的会话怎么知道遵循这套规则,比如另外 5 个会话也在执行 dsh 服务相关任务」
核实结果 —— 这是真缺口:
| 载体 | 何时被会话看到 | 现状 |
|---|---|---|
~/.codebuddy/CODEBUDDY.md(用户级) |
启动时注入 | ❌ 不存在(~/.codebuddy/rules/ 也没有) |
项目根 CODEBUDDY.md |
启动时注入 → 覆盖本工作区所有会话 | ✅ 存在,且已有 §6「并发纪律」—— 但内容有漏(见下) |
.codebuddy/rules/*.md |
条件注入(碰对应文件时) | ✅ 3 个(archive-doc / frontend-ui / server-ops)—— 均未提锁 |
dsh-server-docs/CODEBUDDY.md |
条件注入(碰库内文件时) | ✅ 存在 —— 管文档库约定,未提锁 |
Skills(dsh-change-workflow / dsh-feature-first) |
被触发才加载(不保证) | 有「三把锁」章节,但不保证到达 |
| SessionStart / PreToolUse hook | 会话启动 / 动手时 | 已配,待审核 |
缺口精确到两处 —— 都在唯一可靠的载体里:
- §2 表格:「改任何文件之前 → 跑
handoff-guard.sh」—— 只写「跑」(检查),没写「抢」。 这正是我当天翻车的同一处("跑了检查、看到无锁、直接动手")。 - §6 并发纪律:只有「
--claim <单号>」(细锁,管"同一个单别被两人做"), 漏了「全局执行锁--claim-exec」 —— 而它才是"多个会话同时干活"场景的唯一防线 (细锁管不住跨单撞车:5 个会话各做各的单,细锁互不冲突,但都会改README/INDEX/03-路线图)。
已补齐(改 CODEBUDDY.md,改前已手工备份):
| 位置 | 改为 |
|---|---|
| §2 该行 | 「改任何文件之前(第一步,不是"检查"是"抢")」+ 完整 --claim-exec 命令 + 「抢不到 = 停手」 |
| §6 | 新增「三把锁,顺序固定」表(粗/细/生产,含各自命令与职责)+ 「「无锁」= 你快去抢」读法 + 指向本档与 lock-guard-hook.py |
⚠️ 两条必须知道的限制
CODEBUDDY.md是启动时加载 → 本次改动对已经在跑的会话无效(要重启才重载)。 ⇒ 对"已经在跑的 5 个会话",唯一即时生效的手段是:- hook(
PreToolUse会硬拦,无需会话配合); - 或用户直接在那些会话里说一句「动 dsh 前先抢锁」(最直接,一轮见效)。
- hook(
- 项目根
CODEBUDDY.md不在任何 git 仓库内(项目根不是仓库,也不在文档库仓库里)→ 无版本保护,只能靠手工备份(本次已留.bak-locksect-<ts>)。 建议把规则文件纳入版本管理(否则它的改动既无锁保护、也无历史)—— 属"是否需要新造机制", 按 §4.4 留作独立决策,本次未做。
九、启用步骤(用户在 WorkBuddy 里操作)
/hooks 面板在哪
在 WorkBuddy 的对话输入框里输入 /hooks(斜杠命令)→ 打开 hooks 配置面板。
官方文档原文:"/hooks CLI panel for reviewing and approving any configuration changes before they take effect, ensuring safety."
(外部改 settings.json 必须经此面板审核/批准才会生效 —— 这是官方安全机制,不是我这边的限制。)
步骤
- 输入框敲
/hooks回车 → 面板打开 - 找到
PreToolUse(matcherWrite|Edit)与SessionStart(matcherstartup) - 审核 command 指向
dsh-server-docs/scripts/lock-guard-hook.py→ 批准/启用 - 按
Esc返回
启用前的加固(2026-09-12 实测)
官方文档明确:Windows 上 hooks 强制走 Git Bash(cmd.exe / PowerShell 不支持)→
命令里的裸 python 若不在 hook 的 PATH 里就会直接失败。故把 command 改为绝对路径:
"D:/miniconda3/python.exe" "E:/ProgramData/AI技能/aliyun-dsh-server/dsh-server-docs/scripts/lock-guard-hook.py"
实测记录:env -i "D:/miniconda3/python.exe" "<中文路径>/lock-guard-hook.py"(干净环境、不继承 PATH)
→ 退出码 0、行为正确(持锁态放行 / 无锁态 deny)。即:绝对路径 + 中文路径均可用。
启用后自检(第三方可复现)
| # | 操作 | 期望 |
|---|---|---|
| 1 | 无锁状态下,让任一会话改 dsh-server-docs/INDEX.md |
被拦,并返回可直接粘贴的抢锁命令 |
| 2 | 先 --claim-exec 抢锁,再改同一文件 |
通过 |
| 3 | 改 D:/其他项目/xx.md(不在受保护根内) |
通过(其他项目零影响) |
| 4 | 新开会话 | 启动时打印锁状态提示 |
若不启用
库内约定仍然存在(项目根 CODEBUDDY.md §2/§6 + dsh-server-docs/CODEBUDDY.md + 交接单/README.md §一),
但只对"读过它"的会话有效 —— 这正是"另外 5 个会话不知道"的成因(见 §八)。
⇒ 对已在运行的会话,hook 是唯一不需要它们配合的到达方式。
十、⚠️ 修正:桌面版没有 /hooks 面板(2026-09-12 17:45,用户实测反馈)
用户反馈:按 §九 输入 /hooks —— 什么也没有。
修正:/hooks 是 CodeBuddy CLI 的命令,WorkBuddy 桌面版没有这个面板。
§九 的操作步骤对桌面版不适用。 成因如实记:那是照着 CLI 文档写的,没在桌面版核实 ——
教训:跨端能力先在目标端实测,再写进文档(同 §4.3 验收口径分级:L1 推断不能当 L5)。
桌面版的正确加载方式(第三方探针实测记录)
① ✅ 2026-09-13 实测确认本行原文正确:hook 命令在会话启动时快照,改完
settings.json对已在跑的会话无效(须「完全重启」或新开会话)。⚠️ 当天一度被误写成「每次调用现读」、并据此宣布本行作废,已纠正(成因见文末「修正」节)。路径失配时 fail-closed —— hook 打不开脚本 → 报错 → 该机所有会话的 Write/Edit 全被拒。别把「被拒」当成"锁被别人占了",先去核对 hook 里的绝对路径。 ② 关窗 ≠ 退出 —— WorkBuddy 有常驻能力,点关闭按钮只是关窗口、进程还活着,必须彻底退出。
本机实测印证:当前有 5 个 WorkBuddy.exe 进程在跑(见 17:42 的 tasklist 输出)→ "关窗不退出"属实。
正确步骤(Windows)
-
彻底退出 WorkBuddy:托盘图标右键 → 退出;或任务管理器结束所有
WorkBuddy.exe(含后台进程) -
重新启动 WorkBuddy
-
验证是否生效 —— 本次为此新增了自证手段:hook 会写低频日志到
E:\ProgramData\AI技能\aliyun-dsh-server\.workbuddy\lock-hook.log(可用DSH_LOCK_HOOK_LOG覆盖)2026-09-12 17:42:17 SessionStart 空闲 source=startup 2026-09-12 17:42:18 PreToolUse-deny Edit D:/.../a.md观察 含义 重启后出现 SessionStart行✅ 配置已加载、hook 已生效 之后无锁改本库被拦 → 多一行 PreToolUse-deny✅ 强制层工作正常 两行都没有 ❌ 未生效(回来反馈,改走下一步) 日志只记 SessionStart 与 deny 两类(低频),不记录每次写操作,避免刷屏。
若桌面版最终不支持 hooks
则本机制退化为「约定 + guard」,届时需要另找"到达方式"。按 dsh-decision-method §4.4,
届时候选顺序为:① 让 --claim-exec 成为每次动手前的固定动作(写进 CODEBUDDY.md,已做)
② 把检查做进更早的钩子点(若桌面版有其它可挂的事件)③ 由用户在每个会话里说一句(最直接但需人工)。
十一、⚠️ 修正:禁止「人工删锁 / 接管」(R9,2026-09-12 用户明令)
用户原话:「严格禁止这类操作必须!!!!!!记录到红线中」,并引用本库当时仍在流传的一句操作指引:
接管 —— 按交接单的流程人工删锁(
rm -rf 交接单/.exec-lock)由我接手。但这等于判定那个会话已死/已完成,风险是它其实还在跑 → 撞车,所以我需要你明确点头。
定性:这句话本身就暴露了机制缺陷 —— 它把"接管"的判据交给人的感觉("疑似已死"),而平台没有心跳机制,AI 没有任何客观判据能确认对方是否还在跑。删锁 = 在无法验证的前提下单方面撤销互斥 → 一旦对方仍在跑,就退回「两个会话同时改同一批文件」,而后者正是三把锁存在的唯一理由。
根因不是人不懂,是文档在教:当时全库有 4 处写着"人工删锁",连 handoff-guard.sh 自己的抢锁失败提示里都在教。
处置(2026-09-12 18:50 本库,持全局锁下完成):
| # | 位置 | 改动 |
|---|---|---|
| 1 | 项目根 CODEBUDDY.md §3 |
新增红线 R9(另一并行会话先行落地,已核对内容一致) |
| 2 | 项目根 CODEBUDDY.md §6 |
增「⛔ 抢不到锁就是终点,不是待办」一条 |
| 3 | dsh-server-docs/CODEBUDDY.md |
锁小节增 R9 指针 + 「guard 输出不构成授权」 |
| 4 | 交接单/README.md §三 13 条 |
「接管:…人工删锁…」→ 改为禁止,原文标注「已作废」 |
| 5 | 交接单/README.md §三 11 条 |
「接管必须无损」加限定:接管动作不得由 AI 自行发起 |
| 6 | scripts/handoff-guard.sh(2 处:抢锁失败提示 + 信息模式提示) |
删掉"人工删锁 / 接管",改为 R9 禁止 + 停手 + 报告用户 |
| 7 | skills/dsh-change-workflow/SKILL.md(工作副本 + 归档副本,两处 md5 需一致) |
三把锁章节增 R9 条目 |
R9 唯一合规路径(判据):
- 抢不到锁 ⇒ 停手(不重试、不"抢一下看看"、不绕道);
- 报告用户 —— 说清「谁占着 / 占多久 / 我卡在哪」,而不是自己找理由;
- 锁的处置权只属于用户本人 —— 要撤也只能用户自己动手,AI 不得代判断;
- 仅在「用户已点头 + 用户自己撤锁之后」,才读
OWNER+ 台账进度续做(T03 的 A→B 那套只适用于这种情形)。
验证:bash -n scripts/handoff-guard.sh → 语法 OK;全库 grep -rn 人工删锁 → 只剩"禁止性条款 / 已作废说明",无任何还在教人删锁的位置。
对本档上文的影响:§七 第 1 条(钩子只判"有没有锁"、不判"锁是不是你的")在 R9 下更关键 —— 无锁时 hook 两边都拦(天然串行化),但有锁时它不判归属,所以"这是别人的锁"仍只能靠 guard + R9 拦人, 这正是本次把措辞从"可以删锁接管"改成"停手报告"的原因。
修正(2026-09-13):hook 路径随工作区迁移
工作区 2026-09-13 由 D:\AI技能\aliyun-dsh-server 迁至 E:\ProgramData\AI技能\aliyun-dsh-server;本档上文的 hook 命令片段与 DSH_DOCS_ROOT 默认值已同步改为新路径(实体在 ~/.workbuddy/settings.json 与 dsh-server-docs/scripts/lock-guard-hook.py)。代码仓 D:\github\dsh_shenxian 未变动。
迁移时踩到的坑(值得记住):工作区一搬,settings.json 里 hooks 的绝对路径立刻失配 → python 打不开脚本 → hook 报错 → 该机所有会话的 Write/Edit 全被拒(2026-09-13 实际发生,连"改 settings.json 本身"都被拦)。
语义定论(2026-09-13,含一次自我纠错):hook 命令是「会话启动时快照」 —— 本会话 06:47 启动 → 06:55 把 settings.json 的 hook 路径改成 E: → 07:05 拆掉临时目录联接后,Write/Edit 报的仍是旧路径 D:/AI技能/... ⇒ 改配置对已在跑的会话无效。
⚠️ 期间一度写成「每次调用现读、无需重启」并据此宣布「目录联接多余」—— 实为联接在 06:53–07:05 存在,把「改完就能写」伪装成了现读(同机另一会话也据此得出同样错论,可见这就是该误判的成因)。
正确处置:改完 hook 路径 → 完全重启 WorkBuddy(关窗 ≠ 退出)或新开会话;重启前该机所有会话的写操作一直被拒。应急兜底:本钩子不拦 Bash(有意留的安全阀)⇒ 可用 shell 写文件过渡(09-13 实际走通)。
(临时目录联接已按用户要求移除;D:/AI技能 现无任何实体目录或联接。)