做 #5048(PR #5572)时在同包扫到的同类形态,按 Prime Directive #10 单开,不在那个 PR 里扩面。
现象
AutomationServicePlugin.materializeDeclaredConnectors 的报错器(packages/services/service-automation/src/plugin.ts:1039)在软路径上把消息字符串交给 logger:
// Report a reconcile problem: fatal (boot) throws; soft (reload) logs.
const fail = (msg: string): void => {
if (opts.fatal) throw new Error(msg);
ctx.logger.error(msg);
};
而两个调用点把底层错误的 message 插进那个字符串:
plugin.ts:1122 — resolveInstanceAuth 失败:fail((err as Error).message)
plugin.ts:1158-1161 — provider factory 抛错:fail(`[Automation] failed to materialize connector instance '${name}' via provider '${provider}': ${(err as Error).message} (ADR-0097).`)
这与 #5048 修掉的形态一模一样:err.message 一旦是多行(ZodError 的 .message 是 issue 数组的 JSON dump,第一行就是一个 [),ObjectLogger.write() 只有第一行带等级前缀,而 serve 的 BootLogCapture.offer() 逐行过滤、把没有前缀的续行全部丢弃 —— 诊断退化成一个 [。
fatal: true 那条路(冷启动)不受影响:它 throw,走 kernel 的失败通道,不经过启动诊断缓冲。受影响的是软路径,即 metadata:reloaded 之后的重新物化 —— 也就是 Studio publish / os dev 重编译之后。
可达性:今天大概到不了,所以是 finding
我查过 packages/connectors/*/src(openapi / mcp / rest / slack),目前没有 provider factory 做 Zod .parse()(只有 openapi 的一处 JSON.parse),所以今天从 factory 抛出 ZodError 的路径我没能证明存在。resolveInstanceAuth 那条也没查到 Zod 解析。
但这个形态的风险不需要 ZodError 才成立:任何多行 err.message 都会被同一台管线截断,而 ADR-0097 明确鼓励第三方写 provider factory —— 第一个在 factory 里用 Zod 校验 providerConfig 的插件(这是很自然的写法)就会撞上。而且撞上时的症状和 cloud#971 一样:症状不可读,所以缺陷得以隐身。
建议的修法
与 PR #5572 同一原则,复用它落地的 helper:
const fail = (msg: string, cause?: unknown): void => {
if (opts.fatal) throw new Error(msg);
ctx.logger.error(msg, cause === undefined ? undefined : describeFlowBindError(cause));
};
(describeFlowBindError 在 packages/services/service-automation/src/flow-bind-diagnostics.ts,PR #5572 引入;若要给 connector 复用,名字值得从 FlowBind* 泛化成 describeThrownForLog 一类,那是这单要顺带定的一个小决定。注意 Logger.error 的签名是 error(message, error?: Error, meta?) —— 第二参是 Error 不是 meta,ObjectLogger.error 在运行时对二者都做了分派,但类型上要走 meta 就得确认契约,不能想当然。)
影响面
Generated by Claude Code
做 #5048(PR #5572)时在同包扫到的同类形态,按 Prime Directive #10 单开,不在那个 PR 里扩面。
现象
AutomationServicePlugin.materializeDeclaredConnectors的报错器(packages/services/service-automation/src/plugin.ts:1039)在软路径上把消息字符串交给 logger:而两个调用点把底层错误的 message 插进那个字符串:
plugin.ts:1122—resolveInstanceAuth失败:fail((err as Error).message)plugin.ts:1158-1161— provider factory 抛错:fail(`[Automation] failed to materialize connector instance '${name}' via provider '${provider}': ${(err as Error).message} (ADR-0097).`)这与 #5048 修掉的形态一模一样:
err.message一旦是多行(ZodError 的.message是 issue 数组的 JSON dump,第一行就是一个[),ObjectLogger.write()只有第一行带等级前缀,而serve的BootLogCapture.offer()逐行过滤、把没有前缀的续行全部丢弃 —— 诊断退化成一个[。fatal: true那条路(冷启动)不受影响:它throw,走 kernel 的失败通道,不经过启动诊断缓冲。受影响的是软路径,即metadata:reloaded之后的重新物化 —— 也就是 Studio publish /os dev重编译之后。可达性:今天大概到不了,所以是 finding
我查过
packages/connectors/*/src(openapi / mcp / rest / slack),目前没有 provider factory 做 Zod.parse()(只有 openapi 的一处JSON.parse),所以今天从 factory 抛出 ZodError 的路径我没能证明存在。resolveInstanceAuth那条也没查到 Zod 解析。但这个形态的风险不需要 ZodError 才成立:任何多行
err.message都会被同一台管线截断,而 ADR-0097 明确鼓励第三方写 provider factory —— 第一个在 factory 里用 Zod 校验providerConfig的插件(这是很自然的写法)就会撞上。而且撞上时的症状和 cloud#971 一样:症状不可读,所以缺陷得以隐身。建议的修法
与 PR #5572 同一原则,复用它落地的 helper:
(
describeFlowBindError在packages/services/service-automation/src/flow-bind-diagnostics.ts,PR #5572 引入;若要给 connector 复用,名字值得从FlowBind*泛化成describeThrownForLog一类,那是这单要顺带定的一个小决定。注意Logger.error的签名是error(message, error?: Error, meta?)—— 第二参是Error不是 meta,ObjectLogger.error在运行时对二者都做了分派,但类型上要走meta就得确认契约,不能想当然。)影响面
[#5048 的类别、另一个接缝;触发条件是软路径(reload 后重新物化)上err.message恰好多行。packages/services/service-automation/src/plugin.ts:1039(fail)、:1122、:1158-1161。[#5048 / PR fix(service-automation): flow 绑定失败的告警改用结构化 meta,不再把 Zod issue 数组塞进单行日志 (#5048) #5572(同类,已修 flow 绑定那四+一处)、[convention] best-effort 降级导致"看起来正常、实则不持久"时不应记 warn——把 #4460 的点状修复定成规则 #4632(被截断的诊断比没有诊断更贵)、ADR-0097。Generated by Claude Code