Files
dsh_shenxian/dsh-server-docs/04-调整方案/70-anysearch插件不兼容致崩溃循环.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

9.6 KiB
Raw Blame History

70 · anysearch 插件与 dsh 0.1.2-rc.1 不兼容 → admin 实例崩溃循环(已止损)

  • 日期:2026-09-12
  • 触发:用户报障「界面上实例正在启动,计数一直 01 01 01」+「正在定位问题插件(1/2):@anysearch/anysearch-dsh(66s)」
  • 结论一句话:插件在 import 阶段就抛错(@deepseek-ai/dsh-llm 未导出 assertNever)→ dsh 的 plugin tree 加载是全或无 → 整个启动中止、进程退出 → 平台崩溃自愈反复重启(指数退避,5 次/10 min 逼近熔断)。摘掉该 bundle 即恢复;与 provider 配置、权限、网络都无关。
  • 状态:✅ 已止损(2026-09-12 15:32 起实例恢复)|⚠️ 待决:AnySearch 这个能力是否保留(见 §八)
  • 关联:档案 64(AnySearch 接入 · §9 曾预警「走候选池会出问题」)、档案 65(provider 托管段,未部署)、档案 20(崩溃自愈熔断)、档案 68(候选池启停链路)

一、现象

观察 用户侧 服务侧
实例启动卡住 「正在定位问题插件(1/2):@anysearch/anysearch-dsh(66s)」 每次重启都在同一插件抛错
进度计数不动 「计数一直 01 01 01」 启动在插件加载阶段中止 → 无后续进度可报
反复重启 页面持续停在启动态 [crash-restart] 退避 1000→2000→4000→8000ms,restartsInWindow 1→4

二、根因(journald 原文)

Error: dsh: plugin tree failed to load: failed to apply loader entry include (cordis:include):
  failed to import loader entry web-search-anysearch (@anysearch/anysearch-dsh):
  The requested module '@deepseek-ai/dsh-llm' does not provide an export named 'assertNever'
  ...
  at .../@deepseek-ai/dsh-tool-web/lib/index.js:5
  import { assertNever } from "@deepseek-ai/dsh-llm";

链条:@anysearch/anysearch-dsh → 依赖 @deepseek-ai/[email protected] → 该包 import { assertNever } from "@deepseek-ai/dsh-llm" → 平台侧 @deepseek-ai/dsh-llm(随 dsh 0.1.2-rc.1,官方包零改动)没有这个导出 → ESM 实例化失败 → plugin tree failed to load → 进程 exitCode: 1。

定性:插件与平台的版本兼容性问题(API 面不匹配),不是配置错、不是权限问题、不是网络问题。

三、止损(2026-09-12 15:31–15:32,exec-session-C)

步 动作 证据
1 备份 profile 配置 home/profiles/web/package.json.bak-anysearch-<ts>(cp -a)
2 从 dsh.profile.bundles 与 dependencies 摘掉 @anysearch/anysearch-dsh 两处 python3 -m json.tool → JSON 合法;两处均无 anysearch 残留
3 等平台自愈下一次拉起(无需人工重启) 15:31:53 起无新 crash-restart
4 验证实例健康 dsh web: http://127.0.0.1:34197/?token=… 已打印;curl 本地 → HTTP 401(服务在跑、待鉴权 = 正常);scope dsh-114801-f798a574.scope active

未做(有意):未重启平台服务、未动 guest 实例(4092b965 全程正常)、未删 .credentials.yaml 里的 ANYSEARCH_API_KEY(留着便于换版本后复用)。

踩坑留痕:本轮前两次尝试用 ssh '<内联 script>' 拼复杂引号 → 命令被引号规则击穿(第 3/4 次同类错误)。再次确认技能里的硬性要求:复杂引号一律「本地写文件 → scp → 执行」,勿在 ssh 里内联拼引号。

⚠️ 兼容性 不是 provider 配置问题:cordis.patch.yml 里没有 anysearch 托管段,所以档案 65 的部署救不了这个故障(65 解决的是「provider 指向缺失」,这里是「插件本体 import 失败」)。

四、为什么「自动拉起」救不了它(回答用户问题 1)

能拉起,但不能自愈:

  • 平台的崩溃自愈确实在工作 —— [crash-restart] 指数退避(1s→2s→4s→8s,上限 30s)+ 窗口熔断(10 分钟内 5 次 → crash-loop-circuit-open,之后停止自动重启等人工介入)。
  • 但重启的前提是「偶发故障」。这里是确定性失败:配置里那个插件每次都会在同一个位置抛错 → 每次拉起都崩 → 一路撞到熔断。
  • 所以判据是:「重启后错误是否变化」。错误一模一样 = 配置/兼容性问题,重启无用;错误随机或只出现一次 = 才可能是自愈能救的场景。

五、为什么进度卡在「01」(回答用户问题 2)

  • 实例启动分两大段:(a) 平台侧启动 + 探活 → (b) dsh 进程内 plugin tree 加载。用户看到的「正在定位问题插件(1/2)」属于 (b) 的逐个加载阶段。
  • dsh 的 plugin tree 加载不做部分成功:任一条 entry import 失败 → plugin tree failed to load → 整棵树放弃、进程退出。所以第 1 个插件就失败时,永远走不到第 2 个,进度计数自然停在 01。
  • 「66s」是平台侧探活等待超时(等不到实例 HTTP 就绪)。

六、红线遵守

  • R8(中断用户须先知会):本次影响面 = 仅 admin 实例 cce6d1cd,且它当时已在崩溃循环、本就不可用;未重启平台服务、未动 guest 实例。修复后该实例可用性由不可用变为可用。
  • R7(禁批量):只改 1 个用户的 1 个配置文件(2 行);改动前 cp -a 留回滚点。
  • 服务器侧操作锁:已持有 fix-anysearch-crashloop(/opt/dsh/state/.op-lock/),完工释放。
  • 未 push 任何仓库;未改平台代码。

七、回滚

P=/var/lib/dshs/users/cce6d1cd-b376-4304-80f0-0e1c58c9ffde/home/profiles/web
cp -a $P/package.json.bak-anysearch-<ts> $P/package.json   # 恢复含 anysearch 的版本(会再次崩溃循环,仅在需要复现时用)

停实例:systemctl stop dsh-114801-*.scope(下次访问自动拉起)。

八、⚠️ 待决(需用户定:这是业务目标,不是技术选型)

问题本身是业务边界:AnySearch 这个能力还要不要?两条路:

选项 做法 代价
A. 保留能力,换兼容版本 找一个与 dsh 0.1.2-rc.1 的 @deepseek-ai/dsh-llm 兼容的 anysearch 版本(或让插件侧不依赖 [email protected]);先在测试用户上验证再投放 需要版本调研 + 一轮验证;期间候选池里的 tgz 仍是坏的
B. 下架 把 _anysearch_anysearch-dsh.tgz 从候选池下架(门户 #/plugins),平台继续用 DeepSeek 官方搜索(现状本来就够用) 零风险;放弃 AnySearch 的搜索质量/成本收益

在决定之前有一个必须做的动作(建议立刻做):候选池里 _anysearch_anysearch-dsh.tgz 对任何用户都是坏的 —— 谁点启用谁崩实例(business-plugins 候选池是「全员可见」的)。建议先下架该条,再慢慢评估 A/B。

另附:.credentials.yaml 里 ANYSEARCH_API_KEY 是明文。若选 B,建议连它一起删(删 3 行,见档案 64 §8.4);若选 A,保留待复用。


九、✅ 处置决定与执行(2026-09-12 18:46–18:56)

9.1 用户决定:B —— 彻底放弃 AnySearch

(承接 §八 的 A/B 待决;用户 2026-09-12 明确选 B。)

9.2 执行(持文档库全局锁 + 服务器侧操作锁;未重启服务、未中断任何在线用户)

步 动作 证据
1 只读核查:候选池条目 / 凭据 / 各用户 profile 残留 池内 _anysearch_anysearch-dsh.tgz(11:16);无任何用户 profile 引用 anysearch;/opt/dsh/users/main/.dsh/.credentials.yaml 只匹配到 client-connection / browser-session → ANYSEARCH_API_KEY 已不存在(§8.4 的"删 3 行"因此无需执行)
2 下架候选池条目(走平台 admin API,非手工改库) DELETE /api/plugins/business/%40anysearch%2Fanysearch-dsh → HTTP 200 {"ok":true};audit_log id 175 delete_business_plugin
3 复查 DB business_plugins 无该行;池内 tgz 已移除;残留计数 0
4 清临时 session(R4) user_agent='poc-curl2' 的临时 session 已清

下架姿势说明:用 mksess.cjs 造临时 admin session(600 s)→ curl -X DELETE 打 127.0.0.1:3080 → 用完即删。 刻意不手工改 SQLite —— 走 API 才会同时落到 audit_log,并顺带执行 PKG_NAME_RE 校验与 tgzPath 一致性检查。

9.3 同类排查(顺手做,未扩大范围)

用档案 71 已部署的判据模块(/opt/dshs/lib/web/plugin-compat.js → checkPluginCompat())对池内存量两条做静态体检 (预检只覆盖"新导入",覆盖不到存量 —— 这是本次顺带补上的盲区):

条目 体检结果 处置
dsh-univer-office level = ok(sawPlatformRef=true、无 findings) 保留(池内现存唯一条目)
@liustack/modlens level = unknown(不声明、也不 import 任何 @deepseek-ai 包 → 静态判不了) ⚠️ 且 audit 有它的 plugin_incident(与 anysearch 同一批)→ AI 自主决定一并下架(代价不对称:下架可逆、保留=谁点谁崩);tgz 已备份 /opt/dsh/backups/plugin-pool-20260912/_liustack_modlens.tgz.20260912-185302;audit_log id 176

9.4 现状与回滚

  • 候选池现存 1 条:dsh-univer-office(已验兼容)。
  • 回滚任一:把备份 tgz 经门户「上传」重新导入候选池即可(DB 行随之重建)。
  • 平台搜索能力:继续用 DeepSeek 官方搜索 —— 即 §八 选项 B 的预期结果。