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 一律写「远程服务器」。
4.0 KiB
4.0 KiB
25 · 实例崩溃循环事故(重复 loader id)+ not_running 浏览器导航兜底
- 日期:2026-09-11
- 触发:用户刷新后看到
{"error":"not_running"}且无自动跳转 - 结论一句话:这是一次自造事故 —— 插件包内的
cordis.patch.yml与 profile 层重复 insert 同一个 loader id → 实例启动失败并崩溃循环(被新上线的熔断拦住),用户停在not_runningJSON。已修:包内改空补丁(单点插入)+ 浏览器导航永不吐 JSON + 并发进场等待在飞实例。 - 状态:已修复并上线(服务已重启;插件 0.1.2 已装到两个 profile)
一、事故链(journald 实证)
Error: dsh: plugin tree failed to load: failed to apply loader entry include (cordis:include):
duplicate loader entry id: workspace-scoped-picker
→ 实例启动即退出 → 自动重启 ×5(1→2→4→8→16s)
[crash-restart] {"event":"crash-loop-circuit-open","restartsInWindow":5,"maxRestartsInWindow":5,...}
→ 熔断生效、停止自动重启、条目标 failed 并移除
→ 用户刷新子域页面 → 代理判定 not_running → 回落到 reply.code(404).send({error:'not_running'})
→ 浏览器显示裸 JSON
根因:把包内 cordis.patch.yml 从"覆盖行"改成 insert: workspace-scoped-picker 后,profile 层也 insert 同一 id → loader 认为重复 id 是致命错误(duplicate loader entry id)→ 插件树加载失败。
✅ 值得记录的正面结果:档案 20 的崩溃熔断按设计工作(5 次后开断,避免无限重启风暴),并且它把实例标
failed后允许用户重新进入(清空计数、给足新预算)。
二、修复
| # | 修复 | 位置 |
|---|---|---|
| 1 | 包内 bundle patch 改为空补丁 [] —— 该行只在 profile 层单点插入,避免双写;插件版本 0.1.2 |
poc/workspace-scoped-picker/cordis.patch.yml |
| 2 | not_running 浏览器导航兜底(档案 25 主体):GET + accept: text/html 时永不返回 JSON;并发进场(另一请求正在拉起 → AlreadyRunningError)改为等待在飞实例的 launch token(≤20s) 再 302;仍拿不到则 302 回门户 |
src/supervisor/proxy.ts |
| 3 | 插件 v0.1.2:面包屑根节点用友好名(不暴露 /var/lib/...)+ 加载标记 [workspace-scoped-picker] loaded root=…(用于确定性验证是否生效) |
poc/workspace-scoped-picker/lib/index.js |
| 4 | (同批)预置默认工作区 seedDefaultWorkspace(见档案 24 后续):编辑器要求"必须选工作区"才能开会话 → 平台直接种 ws |
src/supervisor/orchestrator.ts |
安装要点(踩坑):
pnpm add file:<tgz>必须带HOME=<userRoot>/ws;- profile 是 pnpm workspace 根时才需要
-w(guest 需要),否则会报--workspace-root may only be used inside a workspace(admin 不需要); - 换包版本(0.1.1→0.1.2)以确保 pnpm 不复用缓存。
三、验证
| 检查 | 结果 |
|---|---|
ci.sh(typecheck + build + 38 单测) |
✅ |
| 两 profile 安装 0.1.2 | ✅ 软链均指向 0.1.2.tgz;包内 cordis.patch.yml 确认为 [] |
| profile patch 幂等 | ✅ 仅一处 insert(另一处为 name: 值) |
| 插件自检 | ✅ 26/26 |
| 服务重启 | ✅ active / 0 残留 / 门户 200 |
| 端到端(实例启动无 duplicate 报错 + 加载标记出现) | ⏳ 待用户进入会话后核对(我会在日志里查 [workspace-scoped-picker] loaded 与 [seed-workspace]) |
四、回滚
| 项 | 回滚 |
|---|---|
| 插件包 | 退回 0.1.1(但 0.1.1 含重复 insert,不要;应退回空补丁版并保持 0.1.2) |
| 编排器 | /opt/dsh/backups/orchestrator.ts.bak-<TS>、proxy.ts.bak-<TS>;或 git revert ce1b3ac → npm run build → 重启 |
| profile patch | <profile>/cordis.patch.yml.bak-* |
五、红线遵守
只改编排器自身代码与自建插件包;未触碰官方 dsh 主程序与缓存;未读取任何用户数据。