# 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:` 必须带 `HOME=/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-`、`proxy.ts.bak-`;或 `git revert ce1b3ac` → `npm run build` → 重启 | | profile patch | `/cordis.patch.yml.bak-*` | ## 五、红线遵守 只改编排器自身代码与自建插件包;未触碰官方 dsh 主程序与缓存;未读取任何用户数据。