Repository navigation
finding(service-automation): builtin/wait-node.ts 里还有五处外来 cause 插进日志 message —— 其中三处是 #4632 亲自标为 error 的耐久性诊断,且已实测被切碎 #5737
Description
Activity
发现分诊轮判级(#4949 纪律):晋级
pm:queue,域domain:services保持。过时前提检查:origin/main
889ae47上packages/services/service-automation/src/builtin/wait-node.ts仍有Cause:拼接式记录(grep 命中 4 处,加正文表列的第五处形态),前提成立;同族的 #5048 / #5575 / #5636 / #5660 / #5661 均已落地,修法模板已确立(外来 cause 落meta,msg只留平台自己的句子)。晋级理由:落点唯一(单文件五处)、验收判据可机械陈述(该文件不再出现把外来 cause 拼进
msg的记录)、且其中三处是 #4632 亲自标为 error 的耐久性诊断 —— 正文实测这些记录在pretty/text格式(os dev/os serve默认)下被切成三个物理行、后两行无级别无时间戳,grep ERROR只捞到不含任何事实的那一行(cloud#971 同形)。这不是观察类:一条运行时诊断在默认日志格式下丢掉全部事实,是今天就能咬到人的可观测性缺陷。本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
认领:PM 循环第 1 轮(services 车道)
会话:session_015a5qkLzpGXhLL2F5gvJ7dD
分支:claude/issue-5737-wait-node-cause-meta
Worktree:objectstack-issue-5737
域:domain:services
文件面:packages/services/service-automation/src/builtin/wait-node.ts+ 复用同包thrown-cause-diagnostics.ts+plugin-startup-log-cause.test.ts(注释简化)+ 新增/调整用例
串行约束已清:今日 58ffcab(#5760)已触该文件,基于合并后 origin/main;同包无其它在飞(#5660 是 engine.ts,另单另轮)
Generated by Claude Code
验收:ACCEPT → PR #5911(CI 23/23 绿,随后转 ready 入队,本座位跟到 MERGED)。
- 五处位点由 dev 自行枚举成立(第五处是 :216 的
(${err.message})括号形态,非Cause:拼写)—— 全集与 issue 表一致;修法照同族模板(bug(service-automation): flow 绑定失败的告警把 Zod issue 数组塞进单行日志,读者只拿到一个孤零零的[#5048/finding(service-automation): connector 物化失败的fail()也是${err.message}单行插值,同 #5048 的类别、另一个接缝 #5575/finding(service-automation): connector 降级路径(#3017)还有两处${err.message}单行插值,是 #5575 之外的第三个接缝 #5636/finding(service-automation): plugin.ts 里还有三处外来${err.message}插进日志 message —— 其中两条是耐久性诊断,正是最不该被切碎的记录 #5661)零新词汇;[convention] best-effort 降级导致"看起来正常、实则不持久"时不应记 warn——把 #4460 的点状修复定成规则 #4632 三处 error 级别一条未动,check:durability-log-level24 缝全响。 - 公开更正一条 PM 假设:派发令的机制假设 1(「行号表已过期,58ffcab 触过该文件」)被 dev 实测证伪 —— 五处仍精确落在 issue 行号上。假设错在 PM 的 churn 读数(文件级命中 ≠ 行级移动),dev 按「假设须实测」条款顶回,是本轮期望的行为。
:98的信封字段判断有测量支撑(helper 鸭子类型读抛出对象,对error?: string无处可读、undefined会实体化)——直接{ error: result.error ?? 'store unavailable' }进 meta、沿用ThrownCauseMeta.error单键、钉空信封回落,接受;「信封字符串本身是引擎两跳插值构造」的可达性记录有价值。- finding(service-automation): registerDegradedConnector 自己那条 warn 仍把 provider 的 reason 插进 message —— #5636 修好 plugin 侧后,同一条冷启动路径上剩下的第四个接缝 #5660 已闭(08-06 04:25Z)的事实修正收到,issue 正文该句已过期;衍生 finding(service-automation): engine.ts 的 resumeInternal 把驱动错误插进 message —— #5737 修完后,这条 resume 路径上仅剩的一处外来 cause 拼接 #5912(engine.ts:3014 resumeInternal 同族残留)已立,留分诊。
- 字节纪律事故自报自纠(裸 ESC → \u 转义)+ 自扫干净,符合
.claude/skills/**的 markdown 不被任何门禁扫描 —— check:nul-bytes 只看 JS/TS,check:doc-authoring 的 ROOTS 不含 .claude/ #4890 处置先例。
经办:services 座位,会话
session_015a5qkLzpGXhLL2F5gvJ7dD(第 1 轮)。
Generated by Claude Code
- 五处位点由 dev 自行枚举成立(第五处是 :216 的
- added a commit that references this issue
on Sep 17, 2026
做 #5661(PR 见其正文)时,在同一条冷启动路径上实测到的旁生发现:
plugin.ts的 probe 接缝修好之后,同一次 boot 的 stderr 上仍然出现一条把整段多行驱动错误塞进 message 的记录 —— 它来自packages/services/service-automation/src/builtin/wait-node.ts,不在 #5048 / #5575 / #5636 / #5660 / #5661 任何一单的范围内。实测证据
给
sys_automation_run的读取注入一个三行的驱动错误后,一次 boot 的 stderr 上有两条记录。第一条(#5661 修好的 probe 接缝)干净:第二条没有:
注意 cause 在 msg 里面。JSON 格式下
JSON.stringify把换行转义了所以还是一行;pretty/text格式(os dev/os serve的默认)下ObjectLogger.write()一次调用只加一个「时间戳 + 级别」记录头,于是这一条变成三个物理行、后两行无级别无时间戳 —— 文件 sink 当成三条独立记录存,grep ERROR只捞到不含任何事实的那一行。这正是 cloud#971 的形态。五处
wait-node.ts:98errorengine.resume()返回的错误信封result.errorCause: ${result.error ?? 'store unavailable'}wait-node.ts:216warnjob.schedule抛出)warn,走 stdoutwait-node.ts:323error#4632耐久性诊断wait-node.ts:346errorengine.resume()抛出#4632wait-node.ts:387errorjob.schedule抛出)#4632后三处的严重性和 #5661 的第 2、3 条是同一档:
pnpm check:durability-log-level扫的 24 个耐久性接缝里就包含rearmSuspendedWaitTimers,而这三条记录的存在理由就是可读性 —— 文本自己写着「every wait/approval paused before this restart will hang indefinitely」。一条被搅烂的耐久性告警恰是运维最需要能 grep 到的(#4632)。与 #5661 的关系,以及为什么单独立单而不是子任务
#5661 的范围是
plugin.ts三处,正文明确列出「刻意不含的两处」,wait-node.ts根本不在其视野内 —— 它是plugin.ts那个 catch 被调用方内部的记录。两单没有依赖关系:#5661 已经落地,这五处照旧。所以按 objectstack#4949 的规矩独立立单,不作子任务。同族已闭:#5048 / PR #5572(flow 绑定四+一)、#5575 / PR #5639(
fail()两处 +ObjectLogger三参分派)、#5636 / PR #5662(degradeConnectorInstance两处)、#5661(plugin.ts三处)。仍开:#5660(engine.ts的registerDegradedConnector自己那条 warn)。修法(与前四单同源,零新词汇)
复用同包
thrown-cause-diagnostics.ts的describeThrownForLog:message 保持单行自足,cause 走 meta。按Logger契约选参数位 ——warn(message, meta?)用第二参,error(message, error?, meta?)用第三参(第二参传undefined,否则每条记录都带栈)。新增字段名不得含key/token/secret/password子串(#5573)。wait-node.ts:98需要单独判断一下:那里的 cause 是engine.resume()的错误信封字段而不是抛出值,describeThrownForLog的入参形状对不上,可能该走{ error: result.error }直接给 meta。改完后确认
pnpm check:durability-log-level仍绿(三处error不得降级、不得改成 rethrow),并注意plugin-startup-log-cause.test.ts里fakeDataEngine的failProbe注释:它现在刻意只让 probe 那次读失败,就是为了绕开本单这条记录;本单修好后那条注释可以简化。可达性
五处 cause 都来自我们不控制文本的地方(数据源驱动、job 服务、engine 的 resume 信封),今天库内的驱动均单行 —— 所以这是
finding而不是事故报告。第一个包装多行 SDK 错误的驱动/job 实现即撞上,第 323 行尤其(数据库驱动多行错误在生态里常见,上面的实测就是照这个形状造的)。