DEBUG.WIKI
IPPeak:从目标地区访问网页,住宅代理、SEO 监测、广告验证
OnlineSIM:在线接收短信,虚拟号码租用
野草云;香港 200Mbps 峰值;不限月流量 · BGP VPS;查看套餐 →

Node 26.12.0 加载钩子报 ERR_INTERNAL_ASSERTION,检查 hook 内的 require(ESM)

Node 26.12.0 在 module.register 加载的 hook 内同步 require 带导入的 ESM 时触发内部断言,说明识别条件、局部导入调整及修复发行边界。

程序升级到 Node.js 26.12.0 后,还没进入业务入口,就在 ModuleJobSync.syncLink 报 ERR_INTERNAL_ASSERTION。如果启动器使用了 module.register(),而它注册的 hook 模块又通过 createRequire() 加载 ESM,应先检查这条组合。已确认的触发环境为 Linux x64;相同加载方式在 26.11.1、26.10.0 正常,不能把结论扩大成所有 ESM 导入都失败。

这个分支有两个同时成立的条件:require() 发生在 hook 模块的加载阶段,被加载的 ESM 至少还有一条 import。hook 文件甚至不必导出 resolve 或 load,仅初始化过程就足以触发。

调用栈里的 hook 线程是关键

module.register() 注册的异步加载钩子在独立线程中运行。主线程里能成功的 require(esm),不能据此证明 hook 线程采用了同一条内部路径。

下面用三个文件说明触发结构,均为示例文件:

// dep.mjs
import 'node:fs';
export const value = 1;
// hooks.mjs
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
const { value } = require('./dep.mjs');
// app.mjs
import { register } from 'node:module';
register(new URL('./hooks.mjs', import.meta.url));

沿真实错误栈找到 hook 文件,确认 require 的目标及其静态依赖。不要只搜索应用入口,也不要把所有异步函数都当成顶层 await。若报的是 ERR_REQUIRE_ASYNC_MODULE,且模块图确实带顶层 await,应走同步 require 遇到异步模块图的处理路径。本文的触发文件可以完全没有顶层 await。

自有 hook 可以先去掉这处同步跨模块加载

若被加载的是自己维护的 ESM,且 hook 不依赖 CommonJS 特有的同步返回或条件导出选择,可以将上例的 hooks.mjs 改成静态 ESM 导入:

// hooks.mjs
import { value } from './dep.mjs';

保留文件其余真正需要的 hook 导出。这段只展示加载入口的改法,不是要把现有 hook 清空。静态 import 会参与模块链接,命名导出也应与目标模块一致,不能机械增加 .default。如果 require 来自第三方启动器,应优先使用它支持的配置或兼容版本,直接改 node_modules 很容易在重装后丢失。

暂时无法调整加载器时,可在项目的 Node 版本约束、CI 和服务启动环境中恢复已验证兼容的组合;26.11.1 是这一具体回归的对照版本,不是长期固定运行时的建议。先核对当前安全更新与依赖支持范围,保存锁文件和完整启动参数,避免只切换终端 Node,服务进程仍使用26.12.0。

合并修复与正式发行要分开核对

问题出在 hook 线程内的同步模块链接:本应同步得到的依赖任务走到了返回 Promise 的路径,触发内部断言。修复已于2026年10月17日合入,调整了 hook 线程上 require 相关请求的解析与任务选择,并增加相同结构的回归检查。

截至2026年10月20日,已核对的最新26系正式版仍是10月14日发布的26.12.0,不能把合并日期当成修复版号。后续更新前应确认目标正式版本确实包含该修复。

恢复后从原启动器运行,检查 hook 初始化、模块导出及业务入口均完成,再验证原有加载规则。只去掉断言,却跳过了需要的 hook,仍可能让模块解析悄悄改变。若撤回改动,应一起恢复 hook 文件、运行版本配置与启动参数。