DeepSeek Harness 插件 failed to import 真凶:process/ 拿到 null

故障排查发布于 2026-10-03作者: DeepSeek Plugin 插件市场
DeepSeek HarnessDSHdsh-app-bootCJS 解析require.resolve.pathsfailed to import插件加载ResolutionRouter
装上带 CJS 依赖的插件后,DSH 启动只报一行 failed to import,不给原因。真因是 CJS 解析钩子把请求剥成裸包名喂给 require.resolve.paths(),该 API 对内置模块名返回 null,for…of 直接崩。

装上一个带 CJS 依赖的插件之后,DSH 启动日志只给你两行、且一个字的原因都没有:dsh: warning: 1 entry did not activate 和 whale_craft (whale_craft): failed to import。 真实错误被宿主吞掉了,用探针插件在加载器上下文里才拿到它:

text
TypeError: createRequire.resolve.paths is not a function or its return value is not iterable
    at ResolutionRouter.routeScoped (…/@deepseek-ai/dsh-app-boot/lib/index.js:1422:58)

真因不是「那个 API 不存在」,而是它的返回值是 null:触发插件 readable-stream@4.7.0 里写了 require('process/')(注意尾斜杠),isBuiltin('process/') 为 false 所以它正常进入解析路由;DSH 随后用 barePackageName() 把请求剥成裸包名 process 再去问 resolve.paths(),命中「核心模块返回 null」这条 Node 语义,紧接着 for…of null 直接崩(#8674)。而 V8 把「不是函数」与「返回值不可迭代」两种情形合并成同一句报错,让这句提示极具误导性——我们分三轮才定位到。 更要紧的是:0.2.0-rc.2 与 master 都还没修,升级解决不了(#8674)。下面按「先分诊 → 根因 → 定位过程 → 版本矩阵 → 修法 → 最小复现」展开,全部给出可粘贴的代码与可复跑的命令。

先分诊:failed to import 为什么不给原因,以及怎么自己拿到

关键结论:这不是依赖缺失,也不是插件写错了——原生 Node 下那句 require('process/') 解析完全正常。 但在怀疑任何东西之前,先把被吞掉的错误逼出来。

1. 认识这条日志的局限

宿主在 inactiveEntries() 里遇到 fiber === undefined 时,只记下一句:

text
dsh: warning: 1 entry did not activate
whale_craft (whale_craft): failed to import

底层 import 异常被完全丢弃。 也就是说,日志能告诉你的只有「哪个条目没激活」,不能告诉你「为什么」。

2. 用探针插件把真实错误写出来

写一个最小探针插件,让它自己声明 dsh.bundle.patch 从而被当作 bundle 加载,然后在加载器上下文里逐个试 import,把异常写进文件:

js
// profile/node_modules/<probe>/index.js
import { appendFileSync } from 'node:fs'

const OUT = '<某处>/probe-result.txt'
const log = (s) => { try { appendFileSync(OUT, s + '\n') } catch {} }

for (const spec of [
  '@deepseek-ai/schemastery',
  '@deepseek-ai/dsh-tools',
  '@deepseek-ai/dsh-llm',
  'mineflayer',
  'whale_craft',
]) {
  try {
    await import(spec)
    log(`OK   ${spec}`)
  } catch (error) {
    log(`FAIL ${spec}`)
    log(String((error && (error.stack || error.message)) || error))
  }
}

export const name = 'probe'
export function apply() {}

结果很有信息量:宿主包全部 OK,失败集中在 mineflayer 及其依赖链上,真实错误就是上面那句 TypeError ... lib/index.js:1422。定位到这一步,方向就从「插件是不是写坏了」转到「宿主的解析钩子在这条链上做了什么」。

3. 一条命令区分「依赖缺失」和「宿主解析问题」

这两个症状都会表现为「某个 require 失败」,但修法完全不同。用纯 Node 跑三行就能分开:

js
const { createRequire } = require('node:module')
const req = createRequire('C:/any/dir/parent.js')

console.log(req.resolve.paths('process'))    // null        ← 内置名
console.log(req.resolve.paths('process/'))   // 18 条搜索链  ← 加斜杠就绕开内置判定
console.log(req.resolve('process/'))         // …/node_modules/process/index.js

第三行能解析出真实的 node_modules/process/index.js,说明包是装着的、原生解析是通的——问题不在依赖,在宿主的解析路由。

根因:剥掉斜杠之后,请求变成了「对核心模块名的查询」

一句话结论:resolve.paths() 对核心模块名返回 null,而调用点直接 for…of,于是 TypeError。

1. 崩溃点

@deepseek-ai/dsh-app-boot 的 ResolutionRouter.routeScoped(0.1.7-rc.2 第 1422 行):

js
for (const searchPath of createRequire(parent).resolve.paths(name)) {   // ← 崩在这

把这条链拆开就是:

text
readable-stream@4.7.0  lib/internal/streams/end-of-stream.js:8
    const process = require('process/')      ← 尾斜杠;该包 dependencies 里确实有 process
        │
        ├─ isBuiltin('process/') === false   → 不被当内置模块拦下,正常进入解析路由
        │
        └─ barePackageName('process/') === 'process'   → 剥成裸包名
                │
                └─ resolve.paths('process') === null   → 命中「核心模块」分支
                        │
                        └─ for (… of null)  → TypeError

2. 那句误导性报错

V8 对「for-of 里的调用表达式」会把两种完全不同的情形合并成同一句话:

text
TypeError: createRequire.resolve.paths is not a function or its return value is not iterable

报错文本指向的 API 其实是存在的,真因是返回值不可迭代(是 null)。正是这个合并让排查多花了两轮(先怀疑 monkey-patch 没带上 resolve.paths,再怀疑 createRequire 用错)。

3. 实测数据(Node v26.7.0 / win32)

表达式结果
resolve.paths('process')null
resolve.paths('process/')18 条搜索链
resolve.paths('fs')null
resolve.paths('fs/')18 条搜索链
resolve.paths('mineflayer')正常数组(非内置名不受影响)
resolve('process/')<profile>\node_modules\process\index.js ✅

规律非常干净:内置名 → null,内置名加斜杠 → 完整搜索链,普通包名 → 正常数组。

4. 为什么 ?? [] 不够(第二个坑)

同一文件第 883 行的同款调用带了 ?? [],其余调用点漏了。但实测表明,只补空数组会把错误换个样子继续存在:

text
// 只加 ?? [] 之后
Error: Cannot find module 'process/'

原因是 localSearchPaths 变成空数组 → cjs.resolveNative([]) 失败 → 那条 require 仍然挂。必须给出正确的搜索链,不能只兜空。

5. 同类写法的分布

同文件同款写法还在 1247 / 1475 / 2019 / 3263;worker 侧 lib/worker/profile-resolution-bootstrap.js 第 310 / 363 行也有。本地规避补丁覆盖两个文件共 9 处调用点——只改崩溃那一行,别的链上仍会重演。

定位过程:四步,供遇到同类问题的人复用

这一节的价值在于流程,不在于结论。 起点很普通:装一个带 CJS 依赖的插件 → 启动只得到一行 failed to import、没有任何原因。

  1. 把被吞掉的错误逼出来。 报错在 loader 内部,普通日志看不到。写探针插件(自己声明 dsh.bundle.patch 被当作 bundle 加载),在 loader 上下文里 try { await import(spec) } catch (e) { 写文件 },逐个试 @deepseek-ai/schemastery、dsh-tools、mineflayer、目标插件本身。结果:宿主包都 OK,mineflayer 与其依赖链失败,真实错误是 TypeError ...(指向 lib/index.js:1422)。
  2. 先试常规兜底,发现不够。 给那句循环加上 ?? [] 后,错误前进成 Cannot find module 'process/'——说明搜索链被清空,只兜空数组是错的方向。
  3. 直接问 Node 要事实。 实测两个调用:resolve.paths('process') → null;resolve.paths('process/') → 18 条搜索链。根因就此明确,同时解释了那句报错为什么具有误导性。
  4. 验证修法并反查上游。 按守卫写 dshResolvePaths()(拿不到就加斜杠重试)替换全部调用点 → 插件正常加载;随后搭最小 home(只装 readable-stream + 探针)对照 0.2.0-rc.2,确认最新发布版同样中招。

两条经验值得单独记下来:

  • 宿主把 import 的真实异常吞成一句话,排障成本高一个数量级;哪怕只在 DSH_DEBUG=1 下带上底层异常,也能省掉上面第 1 步。
  • 类型断言会掩盖运行期风险:master 里这些调用写的是 resolve.paths(name) as string[],as 是编译期谎言,运行期 null 照样穿过类型检查。

版本矩阵:这不是「升级就好」的问题

先给结论:0.1.7-rc.2 实测复现、0.2.0-rc.2(npm 最新发布)实机复现未修、master 源码同样未修。

版本结论依据
0.1.7-rc.2实测复现本机启动 whale_craft 即 failed to import;探针拿到 TypeError(lib/index.js:1422)
0.2.0-rc.2(npm 最新发布)实机复现,未修① 与 0.1.7-rc.2 逐行 diff:只有 1 个 hunk,且是无关的 bundle 列表新增(@deepseek-ai/dsh-experimental-schedule-bundle);routeScoped 函数体 63 行逐字节相同;无守卫的 resolve.paths(name) 仍在 1248 / 1423 / 1476 / 2020 / 3264。② 最小 home 实机验证:仍抛同一句 TypeError、同一调用栈,只是行号变 1423
master(源码)同样未修packages/boot/app-boot/src/profile-resolution/resolver.ts 第 244 / 471 / 524 行为同写法,仅多一个 as string[] 断言;全文件 Array.isArray 出现 0 次,?? 均不在这些调用点。且该文件最近一次改动是 2026-09-23,此后未再变动
发布记录无相关修复最新 tag dsh-v0.2.0-rc.2;release notes 未提及 resolve.paths / CJS 解析

这不是新问题。 讨论区用 resolve.paths 搜索得到 19 条结果,其中至少 5 条是同源报告,触发包各不相同、跨度从 0.1.6-alpha 到 0.1.7-rc.2,且没有任何一条拿到修复或维护者表态:

#日期触发链亮点
#703109-18jsdom → whatwg-url → tr46 → require("punycode/")最早;已指出「本地加 ?? [] 后错误前进到 Cannot find module 'punycode/'」;0.1.6-alpha.1 vs .2 对照
#737709-21(+09-30 评论)ExcelJS → process/(另有 string_decoder/)给出三行最小复现;09-30 补充:0.2.0-rc.2(桌面版)仍受影响
#790309-26jsdom → … → punycode/明确「0.1.7 重写的 ResolutionRouter 不再守卫 resolve.paths()」;0.1.5-rc.2 正常 vs 0.1.7-rc.2 失败
#791109-26顶层调用 createRequire(...).resolve.paths()解释「劫持后的 require.resolve 未携带原生 resolve.paths」;点名 whale_craft、行号 1422
#825009-29静态依赖 msedge-tts探针式逐包二分;指出「全新 DSH_HOME 正常、旧 home 失败」的环境差异

顺带记一个方法论教训:报告人初版曾写「此前无人报过」,因为他只用 GitHub 的 REST 搜索(只覆盖 issue 与 PR)得到 0 命中——REST 搜索不覆盖 Discussions。改用讨论区搜索后是 19 条。在 GitHub 上判断「是否已有人报过」,必须包含 Discussions。

修法:三种守卫,从最小改动到长期方向

修法 A(最小改动,1 行):加斜杠重试

js
const paths = resolve.paths(name) ?? resolve.paths(name + "/")

实测拿到 18 条搜索链。原理是尾斜杠让名字不再被判定为核心模块,从而走回正常的搜索链计算。

修法 B:用 Node 自己的算法兜底

js
const paths = resolve.paths(name) ?? Module._nodeModulePaths(dirname(parent))

实测得到 15 条搜索链。这个文件别处已经在用 _nodeModulePaths,所以风格上是一致的。

修法 C(本地规避补丁):统一守卫函数,替换全部调用点

把两个文件里的 createRequire(x).resolve.paths(y) 全部换成守卫版本:

js
/**
 * 守卫 require.resolve.paths 的两种失败:
 *   1) 函数不存在 → 返回 fallback
 *   2) 返回 null(核心模块名)→ 加斜杠重试绕开内置判定
 * 注意:不能只 `?? []`,否则同一条 require 会前进成 Cannot find module。
 * 参照 jestjs/jest#16052 的守卫写法。
 */
function dshResolvePaths(req, name) {
  try {
    const resolvePaths = req && req.resolve && req.resolve.paths
    if (typeof resolvePaths === 'function') {
      const value = resolvePaths.call(req.resolve, name)
      if (Array.isArray(value)) return value
      const retry = resolvePaths.call(req.resolve, name + '/')   // ← 绕开内置判定
      if (Array.isArray(retry)) return retry
    }
  } catch {
    /* 拿不到就让调用方回退到原生解析 */
  }
  return []
}

接入要点:

  • 0.1.7-rc.2 共 9 处,分布在 lib/index.js 与 lib/worker/profile-resolution-bootstrap.js;
  • 0.2.0-rc.2 位置完全对应(lib/index.js 行号 +1),改写方式相同;
  • 这是改宿主运行时文件的做法,DSH 升级后会被覆盖(新版本目录是另一份),需要重打;
  • 上游修好后建议删掉;补丁只影响解析回退路径,resolve.paths() 正常返回数组时行为与上游完全一致。

打补丁后的实测效果

阶段结果
打补丁前只有 dsh: warning: 1 entry did not activate + whale_craft (whale_craft): failed to import(真错误被吞)
打补丁后探针逐项 OK:@deepseek-ai/schemastery / dsh-tools / dsh-llm / mineflayer / whale_craft;启动日志不再有 failed to import;插件自身日志出现「插件已加载|工具注册中…」,29 个 mc_* 工具全部可用

长期方向:别 monkey-patch Module._resolveFilename

这类对 CJS 解析器的 monkey-patch 有已知的生态风险,官方替代是:

js
// Node ≥22.15 / 24 稳定
import { registerHooks } from 'node:module'
registerHooks({ resolve(/* … */) { /* … */ } })

最小复现:两种,都不依赖具体插件

要给维护者报,或者要自己确认「是不是同一条」,用下面任一种,两分钟就能跑。

方式一:零依赖、跨机器可跑的纯 Node 脚本

js
#!/usr/bin/env node
import { createRequire, _nodeModulePaths } from 'node:module'
import { dirname } from 'node:path'
import { fileURLToPath } from 'node:url'

const argv = process.argv.slice(2)
const parentArg = argv.indexOf('--parent')
const parent = parentArg >= 0 && argv[parentArg + 1] ? argv[parentArg + 1] : fileURLToPath(import.meta.url)

const req = createRequire(parent)
const line = (label, value) => console.log(`  ${label.padEnd(34)} → ${value}`)

console.log('① 关键数据(这就是根因)')
for (const name of ['process', 'process/', 'fs', 'fs/']) {
  const value = req.resolve.paths(name)
  line(`resolve.paths(${JSON.stringify(name)})`,
    Array.isArray(value) ? `${value.length} 条搜索链` : String(value))
}

console.log('\n② 原生解析本身是好的(说明不是依赖缺失)')
try { line("resolve('process/')", req.resolve('process/')) }
catch (error) { line("resolve('process/')", `${error.code ?? error.name}: 本机没装 process 包(不影响结论)`) }

console.log('\n③ 复刻 DSH 的写法:把请求剥成裸包名后直接 for...of')
const bare = 'process'   // barePackageName('process/')
try {
  for (const searchPath of req.resolve.paths(bare)) void searchPath
  console.log('  …没崩(本机环境与报告环境不同)')
} catch (error) {
  line('for (… of resolve.paths(bare))', `${error.constructor.name}: ${error.message}`)
}

console.log('\n④ 两种建议修法(都能拿到可迭代的搜索链)')
const fixedA = req.resolve.paths(bare) ?? req.resolve.paths(`${bare}/`)
line('A. paths ?? resolve.paths(name + "/")', Array.isArray(fixedA) ? `${fixedA.length} 条搜索链 ✅` : String(fixedA))
const fixedB = req.resolve.paths(bare) ?? _nodeModulePaths(dirname(parent))
line('B. paths ?? _nodeModulePaths(dirname)', Array.isArray(fixedB) ? `${fixedB.length} 条搜索链 ✅` : String(fixedB))

console.log('\n⑤ 反例:只加 `?? []` 是不够的')
const empty = req.resolve.paths(bare) ?? []
line('paths ?? [] 得到的搜索链长度', `${empty.length}  ← 搜索链为空时,同一条 require 会变成 Cannot find module`)

跑法(--parent 指向任意位于 node_modules 深处的文件即可):

bash
node repro-resolve-paths.mjs --parent ".../node_modules/readable-stream/lib/internal/streams/end-of-stream.js"

本机输出(Node v26.7.0 / win32):

text
① 关键数据(这就是根因)
  resolve.paths("process")           → null
  resolve.paths("process/")          → 18 条搜索链
  resolve.paths("fs")                → null
  resolve.paths("fs/")               → 18 条搜索链
③ 复刻 DSH 的写法:把请求剥成裸包名后直接 for...of
  for (… of resolve.paths(bare))     → TypeError: req.resolve.paths is not a function or its return value is not iterable
⑤ 反例:只加 `?? []` 是不够的
  paths ?? [] 得到的搜索链长度         → 0  ← 搜索链为空时,同一条 require 会变成 Cannot find module

方式二:最小 profile 复现(走真实加载器,不装任何第三方插件)

四步搭一个干净 home:

  1. 建目录 <home>/profiles/web/plugins/cjs-probe/;
  2. 写 profile 的 package.json:
json
{
  "name": "dsh-profile-web",
  "private": true,
  "dependencies": {
    "readable-stream": "4.7.0",
    "cjs-probe": "file:plugins/cjs-probe"
  },
  "dsh": {
    "profile": {
      "bundles": [
        "@deepseek-ai/dsh-base",
        "@deepseek-ai/dsh-web-app",
        "cjs-probe"
      ]
    }
  }
}
  1. 写探针三件套——index.js 负责 require('readable-stream') 并把异常写文件;package.json 必须声明 dsh.bundle.patch,否则 bundle 不会被加载:
json
{
  "name": "cjs-probe",
  "version": "0.0.1",
  "private": true,
  "type": "module",
  "main": "index.js",
  "dsh": { "bundle": { "patch": "./cordis.patch.yml" } }
}
yaml
# cordis.patch.yml
- insert:
    - id: cjs-probe
      name: cjs-probe
js
// index.js
import { appendFileSync } from 'node:fs'
import { createRequire } from 'node:module'

const OUT = '<某处>/minimal-result.txt'
const log = (s) => { try { appendFileSync(OUT, s + '\n') } catch {} }
log(`===== cjs-probe @ ${new Date().toISOString()} =====`)

const req = createRequire(import.meta.url)
try {
  const rs = req('readable-stream')
  log('OK   require("readable-stream") 成功  version=' + (rs && rs.Writable ? 'has Writable' : '?'))
} catch (e) {
  log('FAIL require("readable-stream")')
  log(String((e && (e.stack || e.message)) || e))
}

export const name = 'cjs-probe'
export function apply() {}
  1. 装依赖并启动:
bash
pnpm install --dir <home>/profiles/web --node-linker=hoisted      # store 指向你自己的
DSH_HOME=<home> node <dsh>/lib/bin.js --profile web --port 3399

预期:结果文件里出现

text
TypeError: createRequire.resolve.paths is not a function or its return value is not iterable
    at ResolutionRouter.routeScoped (…/@deepseek-ai/dsh-app-boot/lib/index.js:1423:58)   # 0.1.7 是 1422

0.2.0-rc.2 原样启动即复现(stack 同上),所以升级到最新发布版并不能规避。

排查注意事项

  1. failed to import 不等于「插件本身有 bug」。先把它依赖链里的 CJS 包找出来,再看宿主解析钩子。
  2. TypeError: X is not a function or its return value is not iterable 要当成两个假设分别验证:本例中函数是存在的,崩的是返回值(null)。
  3. 别停在 ?? []。它把 TypeError 换成 Cannot find module,看起来「换了个错」,实际是同一个坑。
  4. resolve.paths() 的返回值有三种可能:数组、null(核心模块)、函数不存在(被劫持过)。守卫必须三个都覆盖。
  5. 只修崩溃那一行不够。同款写法在两个文件共 9 处,替换时要全部覆盖。
  6. 注意 as string[] 这类编译期断言。它让 null 通过类型检查,是这类 bug 能长期存活的直接原因。
  7. 改宿主运行时文件会被升级覆盖。打补丁要记录位置,升级后重打;上游修好后删掉。
  8. 判断「是否已有人报过」时要搜 Discussions。REST 搜索只覆盖 issue/PR,会漏掉整类报告。
  9. 报问题时附可复跑脚本。本例这类「装某个具体插件才会触发」的问题,一个零依赖复现脚本能显著提高被处理的概率。
  10. dsh-why 一类诊断工具可能还不认识这个模式(它当前的规则库只覆盖客户端模块表那一类错误)。判别特征是:报错里出现 resolve.paths,且插件带 CJS 依赖。

来源

文中所有行号、diff 结论、搜索链条数与补丁前后效果均为报告人在本机的实测记录(Node v26.7.0 / win32);0.2.0-rc.2 的「逐字节相同」与 master 源码核实亦来自同一份报告。


排查「插件装上却加载不起来」这类问题时,最麻烦的往往是确认到底是哪个插件、哪个版本、哪条依赖链在出问题——尤其当启动日志只给一句 failed to import 的时候。DSH Plugin Hub 提供插件市场、已安装插件列表、自定义安装、设置与系统日志五个界面:已安装列表会标注每个插件的来源(目录插件 / 自定义安装)、版本号与更新时间,并可直接定位到安装目录;系统日志页则按分类与级别记录安装、卸载与诊断轨迹,支持导出全文。先用它把「装了什么、什么版本、装在哪」对齐,再去逐条链地查解析问题,能省掉不少来回。

DSH Plugin Hub 已安装插件列表:按来源筛选、显示版本与更新时间,并可直接在文件管理器中定位

常见问题

插件启动只报 `failed to import`,什么原因都没有,怎么把真实错误挖出来?

写一个「探针插件」:让它自己声明 dsh.bundle.patch 从而被当作 bundle 加载,然后在加载器上下文里 try { await import(spec) } catch (e) { 把 e.stack 写进文件 },逐个试宿主包(如 @deepseek-ai/schemastery、dsh-tools)、目标插件本身、以及它依赖链上的包。宿主包通常都 OK,失败会集中在带 CJS 依赖的那条链上。之所以必须这样做,是因为宿主在 fiber === undefined 时只记下一句 error: "failed to import",真正的 import 异常被丢掉了,普通日志里看不到。

为什么 `resolve.paths()` 会返回 null?这不是 Node 的 API 有问题吧?

不是。Node 文档明确写着:require.resolve.paths(request) 在 request 是**核心模块**时返回 null。这里的关键是被查询的名字:触发方写的是 require('process/')——**带尾斜杠**,因此没被 isBuiltin() 拦下、正常进入路由;而 DSH 用 barePackageName(request) 把它剥成裸包名 process 之后再去问 paths,就命中「核心模块」分支拿到 null,紧接着 for…of null 抛出 TypeError。根因是宿主少了一步守卫,不是 API 异常。

直接在循环前加 `?? []` 不就行了吗?

不行,这是我们实测走过的弯路。加 ?? [] 之后错误会从 TypeError 变成 Error: Cannot find module 'process/'——因为搜索链被清空,cjs.resolveNative([]) 拿不到任何候选路径,那条 require 依然挂。**必须回退到真实的搜索链**:要么给名字加一个斜杠重试(resolve.paths(name + "/"),实测拿到 18 条链),要么用 Node 自己的算法(Module._nodeModulePaths(dirname(parent)),实测 15 条链)。

升级到最新版能解决吗?

不能。0.2.0-rc.2 与 0.1.7-rc.2 逐行 diff 只有 1 个 hunk,且是无关的 bundle 列表新增(@deepseek-ai/dsh-experimental-schedule-bundle),routeScoped 函数体 63 行**逐字节相同**,无守卫的 resolve.paths(name) 仍在(行号 1248 / 1423 / 1476 / 2020 / 3264)。用最小 home(只装 readable-stream@4.7.0 + 一个探针插件)实机启动 0.2.0-rc.2,仍抛同一句 TypeError、同一调用栈,只是行号从 1422 变 1423。master 源码同样未修。

这个 bug 是不是只影响 `readable-stream`?我是不是换个插件就好了?

触发者不止一个。讨论区用 resolve.paths 搜索有 19 条结果,其中至少 5 条同源报告,触发链各不相同:jsdom → whatwg-url → tr46 → require("punycode/")、ExcelJS → process/(另有 string_decoder/)、msedge-tts 的静态依赖树等,跨度从 0.1.6-alpha 到 0.1.7-rc.2,且都没有拿到修复。共同点是「带 CJS 依赖、且依赖链里对内置模块名做了带斜杠的 require」。所以换插件只能躲开这一个触发者,不能修好宿主。

相关术语

require.resolve.paths(request)
Node 的 CJS 解析 API,返回「按当前父模块位置去解析 request 时,会依次查找哪些目录」的数组;**当 request 是核心模块时返回 `null`**。正因为返回值可能是 `null` 而非空数组,任何直接 `for…of` 的写法都必须在拿到值之后先判类型。— https://github.com/deepseek-ai/deepseek-harness/discussions/8674
barePackageName()
DSH 解析路由里用于把请求规范成「裸包名」的函数:`'process/'` → `'process'`。它抹掉了尾斜杠,也就抹掉了 `isBuiltin('process/') === false` 这个「绕过内置判定」的副作用——剥名之前请求能被当普通包路由,剥名之后就变成了对核心模块名的查询。— https://github.com/deepseek-ai/deepseek-harness/discussions/8674
ResolutionRouter.routeScoped
`@deepseek-ai/dsh-app-boot` 里决定「某个 require 该按哪个作用域去搜索」的路由函数(0.1.7-rc.2 `lib/index.js:1422`、0.2.0-rc.2 对应 1423)。它通过 monkey-patch `Module._resolveFilename` 接入 CJS 解析链。本文的崩溃点就在它内部对 `createRequire(parent).resolve.paths(name)` 的 `for…of`。— https://github.com/deepseek-ai/deepseek-harness/discussions/8674
被吞掉的异常(failed to import)
宿主在 `inactiveEntries()` 里遇到 `fiber === undefined` 时,只记录 `error: "failed to import"`,不带底层异常。后果是启动日志里只剩两行没有任何原因的提示(`1 entry did not activate` + `failed to import`),真实 TypeError 完全不可见——排障成本因此高一个数量级,本次光是拿到那句话就花了三轮(探针插件 + 分步守卫)。— https://github.com/deepseek-ai/deepseek-harness/discussions/8674

来源