Repository navigation
Description
现象
宿主 app 声明了 @objectstack/service-cluster / @objectstack/service-cluster-redis(#4719 契约满足)、设 OS_CLUSTER_DRIVER=redis 走 os serve,boot 拒启:
✗ Cluster driver "redis" is not registered. Did you forget to import @objectstack/service-cluster-redis or call registerClusterDriver()? See content/docs/kernel/cluster.mdx §6.
而手动在同一宿主目录 import('@objectstack/service-cluster-redis') 完全正常(导出齐全)。serve 侧没有任何线索:装载失败被空 catch 吞掉。
- 实测环境:平台 17.2.0(objectos-ee:4.1.1 镜像,builtAt 2026-08-24),宿主 app 为 os-tianshun-ehr 的 EE 薄扩展镜像(经 file: 声明 + 符号链接指向镜像内 workspace 包)。
- 复现与临时绕法收录在 steedos-labs/os-project-titanwind-ehr 的
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"条件:hr.resolve('@objectstack/service-cluster-redis') → …/service-cluster-redis/dist/index.cjs ← CJS 构建 hr.resolve('@objectstack/service-cluster') → …/service-cluster/dist/index.cjs -
于是
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,预载脚本在宿主目录以纯 ESM await import('@objectstack/service-cluster-redis') 一次 —— Node 默认 realpath 到与 Runtime 相同的 ESM 实例,注册即生效。实测 3 副本集群恢复正常(redis fence 锁选主、跨副本协调均工作)。
建议修复方向(任一即可,前两者可叠加)
driverRegistry(以及同类模块级单例,如 multi-node gate 的注册表)挂 globalThis 兜底,双构建实例共享一张表;createHostImporter对已声明包在 ESM 上下文优先import条件解析(或 resolve 后按 import 条件重定向),避免 CJS/ESM 双实例;- serve 的集群驱动装载不再静默吞错:至少把失败原因降级为 WARN 打出来 —— "may already be registered by the loaded config" 这个假设应该验证(查 registry)而不是假定。
波及面
不止 cluster:凡经 hostImporter 装载、靠模块副作用注册进模块级单例的包,双构建下都可能注册错位。已另立 cloud 侧 issue 追多节点授权门(registerMultiNodeGate)的同型错位与接线缺口(编号见评论/引用)。
Activity
baozhoutao commentedon Aug 30, 2026
cloud 侧连带缺口(多节点授权门 registerMultiNodeGate 的同型双实例错位 + 只在 EE config 接线导致薄扩展/制品路线不强制)已立:objectstack-ai/cloud#1752
os-warren commentedon 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上复核了 ③,卡片属实且比它写的更明确:这条 catch 是有意的,且注释写下了它的两条理由。卡片的第 ③ 条建议正中要害:两条理由都是假设而不是读数 —— 「可能已经被 config 注册过了」是可以查 registry 证实的,「驱动缺失是文档化的降级」也是可以区分的。派单时请把这一点写进去:修复不是「去掉 catch」,是把假设换成读数(import 后查 registry;查不到才决定是降级还是报错)。⛔ 不要改成直接抛 —— 缺驱动降级到 in-memory 是文档化行为,直接抛会把一个可用部署变成拒启。
另一处历史读数:
serve.ts:711的文档块已经记过同一症状(OS_CLUSTER_DRIVER=redis镜像启动时Cannot find package)。派单前读那段,它可能已经排除掉一部分假设。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
os-warren commentedon Aug 31, 2026
分诊更正 —— 本卡与 #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 的适用差别;registerMultiNodeGate同型错位)的指针。上一条评论里关于「⛔ 不要把 catch 改成直接抛」「把假设换成读数」「
serve.ts:711有历史读数」「与 #13463 是兄弟不是重复」这几点也一并写进了 #13330 的派单说明。请到 #13330 跟进。 若认为两张卡实际是不同缺陷(例如你在两次复现里看到的失败点确实不同),说一声,我重开本卡并把两者的差别写清楚 —— 合并是我按现有证据下的判断,不是终局。
Generated by Claude Code