Skip to content

[Bug] 生成代迁移必败:hoistHostSingletons 会把 @deepseek-ai/ 前缀插件自身的包从生成代删掉(validateGeneration ENOENT) #402

Open

Description

@jiangxiao642-spec

环境

  • DSH Desktop 0.8.2(Windows 10 x64,官方安装包)
  • 内置 dsh-desktop-market-installer(随 0.8.2)
  • profile:web;迁移候选(来自 profile dependencies):@deepseek-ai/dsh-code-runtime、@deepseek-ai/dsh-tools、zod

症状

冷启动维护执行生成代迁移,对第一个候选(@deepseek-ai/ 前缀插件)install/promote 成功后立即失败,回滚并进入 6 小时 defer:

[desktop] migration: moving 3 plugin(s) to generations: @deepseek-ai/dsh-code-runtime, @deepseek-ai/dsh-tools, zod
[desktop] generation-install: installing 0.1.0-rc.8 into staging
[desktop] generation-install: installed in 1506ms
[desktop] generation-install: hoisted 1 host singletons: @deepseek-ai/dsh-code-runtime…
[desktop] generation-install: promoted to deepseek-ai+dsh-code-runtime+0.1.2-rc.1+0624d5844169
[desktop] migration failed, restoring the pre-upgrade profile: ENOENT: no such file or directory, open 'C:\Users\<user>\AppData\Roaming\dsh-desktop\harness\profiles\.generations\live\deepseek-ai+dsh-code-runtime+0.1.2-rc.1+0624d5844169\node_modules\@deepseek-ai\dsh-code-runtime\package.json'
[desktop] profile maintenance frozen: migration deferred (ENOENT: ...)

关键点:promoted to 已成功,随后却在新生成代里找不到插件自己的 package.json——错误路径就是 node_modules/@deepseek-ai/dsh-code-runtime/package.json。

根因(代码级)

packages/dsh-desktop-market-installer/generations/installer.mjs:

  1. hoistHostSingletons(stagingDir) 通过 walkGenerationPackages 遍历生成代内所有 node_modules,isHostSingleton(name) 命中(/^@deepseek-ai\//)的包一律删除。
  2. 该清理没有排除『生成代的宿主插件自身』:当插件名本身以 @deepseek-ai/ 开头时(如 @deepseek-ai/dsh-code-runtime、@deepseek-ai/dsh-tools,它们是常见的 profile 依赖),walk 会命中插件根并把插件自己删掉。
  3. installGeneration 对插件包的 existsSync 检查发生在 hoist 之前,所以 install 返回 ok;直到 validateGeneration 读 <generation>/node_modules/<plugin.name>/package.json 才爆 ENOENT → 迁移整体回滚 + defer。
  4. 实测佐证:hoisted 1 host singletons: @deepseek-ai/dsh-code-runtime… 里被删的名字,正是随后 validate 找不到的那个;生成代目录下 node_modules/@deepseek-ai/ 为空。

(hoist 删除嵌套的 @deepseek-ai/* 副本是预期行为;这里的问题是根插件也被一并删了。)

影响

建议修复

在 hoist 时排除生成代自身的根插件:installGeneration 内本就有 pluginName,可传给 hoistHostSingletons 并在 walk 时跳过 join(generationDir, 'node_modules', pluginName);或改为只 hoist 嵌套副本、不触碰顶层插件根。修复后 @deepseek-ai/* 前缀插件即可正常迁移。

(现场已临时用 .generations-migrated 标记冻结迁移以规避,未在本机验证修复补丁。)

Activity

  1. jiangxiao642-spec commented on Sep 12, 2026

    @jiangxiao642-spec
    Author

    补充:hoist 之外还有一道生成代准入坎(收窄上文一处预期)

    我们继续在本机追了整条迁移链,结论需要收窄上面「修复后 @deepseek-ai/* 前缀插件即可正常迁移」的说法:修好 hoistHostSingletons 的自吃问题后,这三个候选依然不会被接受为生成代——它们过不了 validateGeneration 的补丁层校验(除非上游为这些包补上 dsh.bundle 声明,而它们本不是组合包,见下)。

    证据一:validateGeneration 对每个迁移候选硬性要求可读的 bundle 补丁(0.8.2 桌面主进程实测,原样摘录):

    const patch = manifest.dsh?.bundle?.patch;
    if (typeof patch !== "string" || !existsSync(join(packageDir, patch))) {
      throw new Error(`${plugin.name} generation has no readable bundle patch`);
    }

    即:候选包必须自带 dsh.bundle.patch 指向可读文件,否则迁移在 validate 阶段以 generation has no readable bundle patch 抛错。

    证据二:三个候选都没有 dsh.bundle 字段。 npm 全版本复核:

    • @deepseek-ai/dsh-tools:已发布 21 个版本,全部无 dsh.bundle.patch
    • @deepseek-ai/dsh-code-runtime:已发布 20 个版本,全部无
    • zod:通用库,无该字段(本机 4.6.2 亦无)

    对照组(真正的组合包):

    • @deepseek-ai/dsh-base:21 个版本全部带 "dsh": {"bundle": {"patch": "./cordis.patch.yml"}}
    • @deepseek-ai/dsh-web-app:同带

    证据三:同一「必须有补丁层」的假设贯穿启动与投影两侧。 dsh-app-boot 解析 dsh.profile.bundles 每个条目时,无 dsh.bundle 会直接以 declares no dsh.bundle 失败(源码注释明言这是有意为之的 fail-loud);projection.mjs 的 declaresBundle() 也只把带补丁的依赖算作组合层(组合进 bundles 的最小口径)。

    小结: 这三个候选不是「组合包(bundle)」,而是 profile 的普通依赖——生成代机制对它们本就不适用。zod 的情况见 #403,三者卡在同一处。

    看起来是两条口径的错位:迁移候选取「profile 的 dependencies 与 dsh.profile.bundles 合并,再剔除保留集(dshmarket、dsh-base、dsh-web-app)之后剩下的全部名字」(本次的三个候选即由此而来);而生成代准入要求「必须声明 dsh.bundle」。普通依赖天然不满足后者。建议一并考虑:

    1. 迁移候选只收声明 dsh.bundle 的包;普通依赖留在 pnpm 依赖集按常规方式升级,不参与生成代。
    2. 市场在更新/迁移路径上对无 dsh.bundle 的包给出明确行为与提示(跳过/说明原因),避免用户走进必失败分支。

    以上均为桌面 0.8.2 / harness 0.1.2-rc.1 / Windows 10 的本地实证。现场我们保留着,需要脱敏日志或目录快照可以随时提供;后续版本修复后我们愿意帮验证。

  2. suizhejiu commented on Sep 13, 2026

    @suizhejiu

    另一台机器上复现了同一机制,补充佐证(插件为 @deepseek-ai/dsh-mcp-client@0.1.1-rc.1,profile web,DSH Desktop 0.8.1 → 0.8.2 均存在):

    [desktop] migration: moving 7 plugin(s) to generations: @deepseek-ai/dsh-mcp-client, ...
    [desktop] generation-install: pinned staging to https://mirrors.cloud.tencent.com/npm
    [desktop] generation-install: installing 0.1.1-rc.1 into staging
    [desktop] generation-install: installed in 8873ms
    [desktop] generation-install: hoisted 3 host singletons: @deepseek-ai/cosmokit, @deepseek-ai/dsh-mcp-client, @deepseek-ai/schemastery…
    [desktop] generation-install: promoted to deepseek-ai+dsh-mcp-client+0.1.1-rc.1+7b10bf89f920
    [desktop] migration failed, restoring the pre-upgrade profile: ENOENT: no such file or directory, open '<DSH_HOME>\profiles\.generations\live\deepseek-ai+dsh-mcp-client+0.1.1-rc.1+7b10bf89f920\node_modules\@deepseek-ai\dsh-mcp-client\package.json'
    [desktop] profile maintenance frozen: migration deferred (ENOENT: ...)
    

    文件系统核实:<DSH_HOME>\profiles\.generations\live\ 下 6 个 generation 的 node_modules\@deepseek-ai\ 全部是空目录,插件自身的包目录确实不在生成代里;而预检又要求 <gen>\node_modules\<pluginName>\package.json 必须存在。

    时间线补充:09-12 03:02 与 11:08 两次自动重试都失败(网络正常,已 pin 镜像,生成代 install/promote 均成功),维护持续冻结,profile 的锁文件至今未被同步。该 profile 的三个官方插件全部是 @deepseek-ai/ 前缀,因此迁移永远不会成功。

    影响面提示:同一校验(verifyGenerationPeers)也被市场安装路径调用(out/main/index.js 中的调用点),因此从市场安装 @deepseek-ai/ 前缀的插件可能同样直接报"包根缺失"。

    建议修复方向:清理宿主包时排除 generation.pluginName 自身(或只清理嵌套 node_modules),预检改为从共享树/安装闭包解析插件自身。

    同一台机器上这次排查的完整记录(含临时缓解与回滚步骤,已脱敏):https://gist.github.com/suizhejiu/b13d0287216d71831cc789e299572173

  3. yaojin3616 commented on Oct 28, 2026

    @yaojin3616
    Contributor

    v0.13.0 发布后状态复核:

    复核发现该报告后续补充包含两层问题:host singleton hoist 对根包的处理,以及普通依赖没有 dsh.bundle.patch 时不应进入组合包迁移。后者仍由未合并 PR #812 跟踪,因此不能整体按 v0.13.0 已修复关闭。待包含该修复的版本发布后,应按普通依赖/组合包分别验证迁移、重启和加载结果。

    版本入口:https://github.com/dataelement/dsh-desktop/releases/tag/v0.13.0

    反馈请隐去 token、账号凭据、完整会话和其它敏感信息。

  4. added
    keep-open排除无反馈自动关闭,适用于已确认未修复或等待交付的问题
    on Oct 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingkeep-open排除无反馈自动关闭,适用于已确认未修复或等待交付的问题

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions