Files
dsh_shenxian/dsh-server-docs/04-调整方案/25-实例崩溃循环修复与not_running兜底.md
T
admin 5ad755116e chore(docs): 文档库并入代码仓(R4 选 a)+ 索引/台账跟进
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 一律写「远程服务器」。
2026-09-15 18:47:13 +08:00

4.0 KiB
Raw Blame History

25 · 实例崩溃循环事故(重复 loader id)+ not_running 浏览器导航兜底

  • 日期:2026-09-11
  • 触发:用户刷新后看到 {"error":"not_running"} 且无自动跳转
  • 结论一句话:这是一次自造事故 —— 插件包内的 cordis.patch.yml 与 profile 层重复 insert 同一个 loader id → 实例启动失败并崩溃循环(被新上线的熔断拦住),用户停在 not_running JSON。已修:包内改空补丁(单点插入)+ 浏览器导航永不吐 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 主程序与缓存;未读取任何用户数据。