Repository navigation
hostImporter CJS/ESM 双构建实例错位:OS_CLUSTER_DRIVER=redis 注册进 CJS registry、Runtime 查 ESM registry → 拒启,且 serve 静默吞错 #13365
Description
Activity
cloud 侧连带缺口(多节点授权门 registerMultiNodeGate 的同型双实例错位 + 只在 EE config 接线导致薄扩展/制品路线不强制)已立:objectstack-ai/cloud#1752
- addedbugSomething isn't workingSomething isn't workingpriority:p1High: required for production / M2High: required for production / M2
on Aug 31, 2026 分诊 →
domain:cli· p1 · bug。跨域单,本座位指定domain:cli为认领车道。锚定。 三个候选修复点分属两个域:②
createHostImporter(packages/types/src/node.ts)与 ③ serve 的静默 catch(packages/cli/src/commands/serve.ts)落domain:cli;①driverRegistry挂 globalThis(packages/services/service-cluster/src/cluster.ts)落domain:services。根因是 ② —— 卡片自己也说了波及面「不止 cluster:凡经 hostImporter 装载、靠模块副作用注册进模块级单例的包」——所以主车道按根因落点定为domain:cli,① 作为跨域半边由该车道 PM 在认领评论里申报完整文件面。在
origin/main上复核了 ③,卡片属实且比它写的更明确:packages/cli/src/commands/serve.ts :2469 // Same host-anchored resolution as the gate above — the shipped :2470 // drivers (`-redis`, `-postgres`, …) are app-declared too. The catch :2471 // stays deliberately silent: the driver may already have been :2472 // registered by the loaded config, and an absent driver is a :2473 // documented fall-back to the in-memory cluster, not a boot failure. :2474 try { await importFromHost(`@objectstack/service-cluster-${__clusterDriver}`); } :2475 catch { /* may already be registered by the loaded config */ }这条 catch 是有意的,且注释写下了它的两条理由。卡片的第 ③ 条建议正中要害:两条理由都是假设而不是读数 —— 「可能已经被 config 注册过了」是可以查 registry 证实的,「驱动缺失是文档化的降级」也是可以区分的。派单时请把这一点写进去:修复不是「去掉 catch」,是把假设换成读数(import 后查 registry;查不到才决定是降级还是报错)。⛔ 不要改成直接抛 —— 缺驱动降级到 in-memory 是文档化行为,直接抛会把一个可用部署变成拒启。
另一处历史读数:
serve.ts:711的文档块已经记过同一症状(OS_CLUSTER_DRIVER=redis镜像启动时Cannot find package)。派单前读那段,它可能已经排除掉一部分假设。⚠️ 与 #13463 的关系:兄弟,不是重复。 #13463 说的是serve.ts:2363的空 catch 吞掉'undeclared'宿主导入分类;本卡是:2474的空 catch 吞掉集群驱动装载失败。两条不同的 catch,同一种病。两张卡都留着,但同一个 dev 连着做最省 —— 派单时请写明这个配对。定级 p1。 现场报告(os-tianshun-ehr EE 薄扩展镜像,平台 17.2.0,builtAt 2026-08-24),后果是三副本集群拒启,且真实原因与报错相隔两层。有绕法(
node --import cluster-preload.mjs,已实测恢复),所以不是 p0;但绕法要求客户改入口,不是配置项。cloud 侧连带缺口已由报告方立在 objectstack-ai/cloud#1752(
registerMultiNodeGate同型双实例错位)。那是 cloud 座位的卡,本卡不代管;若两边同批修,请走跨座位路径而不是在一个 PR 里跨仓。
Generated by Claude Code
分诊更正 —— 本卡与 #13330 是同一条缺陷,合并到 #13330(正卡),本卡作为重复关闭。
我上一条评论把本卡定为
domain:clip1 时还没读到 #13330。两张卡描述的是同一件事:同一个importFromHost接缝、同一次 CJS/ESM 双构建实例错位、同一条serve.ts静默 catch、同一个Cluster driver "redis" is not registered症状,同一位报告方。#13330 早 3 小时 15 分立卡,且带决定性读数 —— 同进程内同时探两个实例,ESM 侧 REGISTERED / CJS 侧 NOT registered —— 所以以它为正卡。本卡独有的两项内容已原样带到 #13330 上,不会丢:
node --import cluster-preload.mjs预载绕法(已实测 3 副本恢复,redis fence 选主与跨副本协调均工作),以及它与 cli: serve's cluster-driver load registers into the CJS registry while the ESM Runtime reads the ESM one — OS_CLUSTER_DRIVER=redis silently downgrades to "not registered" (post-#10645) #13330 那条objectstack.config.ts加一行 import 的适用差别;- cloud 侧连带缺口 objectstack-ai/cloud#1752(
registerMultiNodeGate同型错位)的指针。
上一条评论里关于「⛔ 不要把 catch 改成直接抛」「把假设换成读数」「
serve.ts:711有历史读数」「与 #13463 是兄弟不是重复」这几点也一并写进了 #13330 的派单说明。请到 #13330 跟进。 若认为两张卡实际是不同缺陷(例如你在两次复现里看到的失败点确实不同),说一声,我重开本卡并把两者的差别写清楚 —— 合并是我按现有证据下的判断,不是终局。
Generated by Claude Code
- removedpriority:p1High: required for production / M2High: required for production / M2
on Aug 31, 2026
现象
宿主 app 声明了
@objectstack/service-cluster/@objectstack/service-cluster-redis(#4719 契约满足)、设OS_CLUSTER_DRIVER=redis走os serve,boot 拒启:而手动在同一宿主目录
import('@objectstack/service-cluster-redis')完全正常(导出齐全)。serve 侧没有任何线索:装载失败被空catch吞掉。deploy-ee/README.md与deploy-ee/cluster-preload.mjs(PR steedos-labs/os-project-titanwind-ehr#1880)。根因链(逐环节核实过)
serve(packages/cli/src/commands/serve.ts,OS_CLUSTER_DRIVER分支)经 importFromHost(createHostRequire认 NODE_PATH,于是 #4699 立下的「host app 必须自己声明」在 pnpm 工作区里根本没被强制 #4719 宿主解析器)装载@objectstack/service-cluster-${driver}。createHostImporter(packages/types/src/node.ts)对已声明的包走hostRequire.resolve(pkg)—— CJS 解析,命中 exports 的"require"条件:于是
registerClusterDriver('redis', …)(service-cluster-redis/src/index.ts末尾的模块副作用)注册进的是 service-cluster CJS 构建实例的driverRegistry—— 这是个模块级const Map(packages/services/service-cluster/src/cluster.ts),不挂 globalThis。Runtime的defineCluster从 ESM 上下文 bare import@objectstack/service-cluster→ ESM 构建实例(dist/index.js)→ 另一张空 Map → 抛 "not registered"。加重:serve 对该 import 是空
catch { /* may already be registered by the loaded config */ }—— 真实失败模式(装载成功但注册进错实例/或解析失败)完全静默,操作者只能看到第 4 步的结果性报错,与真实原因相隔两层。一句话:同一个包的 CJS 与 ESM 双构建各持一份 registry,注册与查找不在同一张 Map 上。
最小复现
任意宿主 app(package.json 声明上述两包并可解析),
OS_CLUSTER_DRIVER=redis OS_REDIS_URL=redis://… os serve→ 拒启如上。项目侧临时绕法(可作修复验证参照)
入口改
node --import cluster-preload.mjs,预载脚本在宿主目录以纯 ESMawait import('@objectstack/service-cluster-redis')一次 —— Node 默认 realpath 到与 Runtime 相同的 ESM 实例,注册即生效。实测 3 副本集群恢复正常(redis fence 锁选主、跨副本协调均工作)。建议修复方向(任一即可,前两者可叠加)
driverRegistry(以及同类模块级单例,如 multi-node gate 的注册表)挂 globalThis 兜底,双构建实例共享一张表;createHostImporter对已声明包在 ESM 上下文优先import条件解析(或 resolve 后按 import 条件重定向),避免 CJS/ESM 双实例;波及面
不止 cluster:凡经 hostImporter 装载、靠模块副作用注册进模块级单例的包,双构建下都可能注册错位。已另立 cloud 侧 issue 追多节点授权门(registerMultiNodeGate)的同型错位与接线缺口(编号见评论/引用)。