Repository navigation
Description
做 #5636 时在同一条 #3017 降级路径上扫到的,但在另一个文件(engine.ts)、另一个方法里,契约也不同,因此不在那一单范围内,按 Prime Directive #10 单开。
现象
packages/services/service-automation/src/engine.ts:1659(origin/main 5b60b3669):
registerDegradedConnector(def: Connector, reason: string, origin: ConnectorOrigin = 'declarative'): void {
const parsed = ConnectorSchema.parse(def);
this.assertSameOriginOrFree(parsed.name, origin);
this.connectors.set(parsed.name, { def: parsed, handlers: {}, origin, state: 'degraded', degradedReason: reason });
this.logger.warn(`Connector registered DEGRADED: ${parsed.name} (origin: ${origin}) — ${reason}`);
}那个 reason 参数就是 #5636 处理的同一个值:plugin.ts 的 degradeConnectorInstance 把
ConnectorUpstreamUnavailableError.message 原样传进来,而该 message 由第三方 provider
factory 构造(ADR-0097 明确鼓励第三方去写 provider factory;spec 只定义错误类、不约束
文本),完全可以是多行。
为什么它比 #5636 修掉的那两处更容易被忽略
调用顺序决定了它先发生,而且发生在常见分支上:
degradeConnectorInstance→engine.registerDegradedConnector(husk, info.reason, …)
→ 这一条 warn(husk 注册成功时就会打,也就是每次首次降级都打);- 只有 husk 注册抛错时才走到 plugin 侧那条 warn(finding(service-automation): connector 降级路径(#3017)还有两处
${err.message}单行插值,是 #5575 之外的第三个接缝 #5636 已修); - 然后才是 plugin 侧的降级公告 error(finding(service-automation): connector 降级路径(#3017)还有两处
${err.message}单行插值,是 #5575 之外的第三个接缝 #5636 已修)。
也就是说 #5636 修完之后,首次降级的默认路径上仍然留着一条会溢出的 warn。
危害机制(与 #5636 同一条,已实测)
ObjectLogger把warn送 stdout,serve的启动静默窗口只包了
process.stdout.write,窗口从 config 加载前一直开到 banner 打印;- 冷启动的
materializeDeclaredConnectors(ctx, { fatal: true })遇到上游不可达是降级、
不抛错,所以这条路径在窗口内就会跑; BootLogCapture.offer()只在classifyBootLogLine能在物理行上找到<ts> <LEVEL>头时
才保留该行,续行直接丢弃。
#5636 的 PR 里对一份 13 行的插值 ZodError dump 实测:写出 13 行,缓冲保留 1 行(止于
Zod [ 的头行)、丢弃 12 行。这里的载荷不是 ZodError 而是 provider 的 message,机制一样。
即 cloud#971 的原始形态。
可达性(为什么是 finding 而不是 bug)
与 #5575 / #5636 同样的理由:今天 packages/connectors/*/src 里 ConnectorUpstreamUnavailableError
的 message 在 openapi / mcp / rest / slack 里都是我们自己写的单行文本。第一个把上游 SDK 的
多行错误塞进该错误类的第三方 provider 插件会撞上。
修法上需要先定的一件事(不是零决策)
与 #5636 的两处不同,这里没有抛出值可用:registerDegradedConnector 的签名只收
reason: string。而且这个字符串同时是 degradedReason,会经 GET /connectors 和
connector_action 被拒时的文本出去 —— #5636 刻意保持它逐字不变(人透过 JSON 读,不经按行
切分的消费者)。所以至少两条路:
- A:签名加一个可选
cause?: unknown,让warn用describeThrownForLog(cause)走 meta,
reason与degradedReason一字不动。调用点(只有 plugin.ts 一处)顺手把err传进来。 - B:这条 warn 不再插值
reason,只报 name/origin,reason 作为 meta 字段
({ degradedReason: reason },注意字段名不得含key/token/secret/password子串,
见 finding(core): ObjectLogger 的脱敏表按子串匹配,一个叫keys的普通字段会被整块换成***REDACTED***#5573)。不动签名。
倾向 A:它与 #5048 / #5575 / #5636 建立的形状一致(抛出值一路带到报告点,不在边界先
拍平成字符串),而且 ConnectorUpstreamUnavailableError 本身还带一个 cause(底层 connect
错误),A 之后才有可能把它也渲染出来。B 更小,但把「reason 是给人读的文本」和「日志要结构化」
这两件事压在同一个字符串上,下一个接缝还会再遇到。
关联
#5636 / PR(plugin 侧两处,同一条路径)、#5575 / PR #5639、#5048 / PR #5572、#5573(脱敏按
子串匹配)、#4632、#3017(降级/重试本身)、cloud#971、ADR-0097。
Activity
os-zhuang commentedon Aug 5, 2026
发现分诊(#4949 纪律):晋级——摘
finding换pm:queue,保留domain:services。一行理由:同族先例 #5048 / #5575 / #5636 已确立形状并全部入队(#5636 上轮晋级),本条是同一冷启动路径上默认分支先发生的那条 warn,可达性与家族一致;修向裁定按 A 路(
registerDegradedConnector签名加可选cause?: unknown,warn 经describeThrownForLog走 meta,reason/degradedReason一字不动)——与家族既定形状同源,B 路正文已自述会把两种职责压在同一字符串上。落点service-automation/src/engine.ts:1659。车道注意:#5636 若仍在飞,同路径(不同文件)按依赖顺序串行。维护者可否决。本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
os-zhuang commentedon Aug 6, 2026
认领(services 车道 PM 派发,
session_01BWS4heBoAitLmzCLhcYdbK)claude/issue-5660-degraded-register-cause../objectstack-issue-5660(dev 子代理按 worktree-first 自建)packages/services/service-automation/src/engine.ts(registerDegradedConnector签名加可选cause?: unknown+ warn 走 meta,立单行号 :1659 随 feat(spec,service-automation,devx): 未声明 resumeAuthority 的 pausing 节点类型不再静默 fail-open (#5561) #5725 落地已漂)、src/plugin.ts(唯一调用点顺手传err)、src/thrown-cause-diagnostics.ts(复用)、同包测试、.changeset/*.md。reason/degradedReason一字不动。resumeAuthoritydefaults to'any', so every future pausing node ships fail-open — ADR-0044 says this is "tracked separately" and nothing tracks it #5561(PR feat(spec,service-automation,devx): 未声明 resumeAuthority 的 pausing 节点类型不再静默 fail-open (#5561) #5725)、plugin.ts 前序 finding(service-automation): plugin.ts 里还有三处外来${err.message}插进日志 message —— 其中两条是耐久性诊断,正是最不该被切碎的记录 #5661(PR fix(service-automation): 启动路径三条日志的外来 cause 改走结构化 meta (#5661) #5738)均已 merged,automation 槽位空。Generated by Claude Code
fix(service-automation): the degrade REGISTRATION logs its cause as m…
fix(spec): 重锚 authorable-surface.base.json 改为显式动作,构建不再顺手推进删除门的锚点 (obj…