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 一律写「远程服务器」。
62 lines
4.0 KiB
Markdown
62 lines
4.0 KiB
Markdown
# 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 主程序与缓存;未读取任何用户数据。
|