Skip to content

finding(service-automation): engine.ts 的 loadSuspendedRun 把驱动错误插进 warn 的 message —— 与 #5912 同文件、同一次失败的另一半,且这条会被 boot 缓冲丢弃 #6230

Closed

Description

@hotlong

做 #5912(PR #6228)时在同一个文件扫到的旁生发现,但在另一个方法、另一条分支上,且 #5912 的分诊评论(2026-08-06 16:57Z)已把那一单范围明确钉死在 :3017 一处、⛔ 不扩面,因此不在那一单范围内,按 Prime Directive #10 / objectstack#4949 单开。

现象

packages/services/service-automation/src/engine.ts:2931(origin/main 1549605f6;⚠️ 行号会漂,以内容定位),loadSuspendedRun —— 也就是 loadSuspendedRunStrict 的降级版读取器:

private async loadSuspendedRun(runId: string): Promise<SuspendedRun | null> {
    try {
        return await this.loadSuspendedRunStrict(runId);
    } catch (err) {
        this.logger.warn(
            `[automation] failed to load suspended run '${runId}' from durable store: ${(err as Error).message}`,
        );
        return null;
    }
}

(err as Error).message 来自 loadSuspendedRunStrict 底下的数据源驱动,和 #5912 是同一个 thrown 值 —— 我们不控制它有几行。它被插进 logger.warn 的 message。

与 #5912 的关系:同一次失败的另一半

两者读的是同一个 loadSuspendedRunStrict。一次「resume 时存储不可达」会同时走这两条:

所以 PR #6228 落地后,这条路径上 stderr 那条已经干净,stdout 这条仍会被换行切碎。PR #6228 的测试正是靠「只抓 stderr」把待验接缝与这条 warn 隔开的(见该 PR 测试文件里 resumeAgainstUnreadableStore 的注释)。

危害机制:比 #5912 那条更重一档

同族基础危害相同 —— ObjectLogger.write() 一次调用只加一个「时间戳 + 级别」记录头,message 里的换行把一条记录变成多个物理行,后几行无级别无时间戳,grep WARN 只捞到不含事实的那一行。

但多一条:ObjectLogger 把 debug / info / warn 路由到 stdout,而 serve 的 boot-quiet 窗口只包了 process.stdout.write,其 BootLogCapture.offer() 仅在 classifyBootLogLine 找得到级别头时才保留该物理行。这条是 warn ⇒ 落在那个缓冲的过滤面上,无头续行是被直接丢弃而不只是被误读。

而且它在 boot 期真实可达:plugin.ts 的 start()(:939)调 rearmSuspendedWaitTimers,后者对 overdue 运行调 engine.resume(run.runId)(builtin/wait-node.ts:378),resume() 的 gate 就走到这个降级版读取器。

对照:#5912 那条是 error 走 stderr,不经该缓冲,危害是被误读;这条是被误读 + boot 期被丢弃。这正是 thrown-cause-diagnostics.ts 模块 docblock 里 warn / error 下游不同的那段所描述的形态,也是 #5661 第 2、3 条与 cloud#971 的形态。

级别本身是对的,不要顺手改

warn 在这里是正确的:这是一个刻意的降级读取器,注释写明它服务于「只需要 best-effort 答案」的顺带读取方(gate 查询、screen 取数),真正需要区分「存储挂了」与「运行没了」的 resumeInternal 用的是严格版。按 #4632 的判据,这是功能性降级而非耐久性降级,不应上调到 error —— 上调才是 #4632 明确警告的镜像错误。本单只谈 message 拼接。

可达性(为什么是 finding 而不是事故报告)

与 #5575 / #5636 / #5661 / #5737 / #5912 同样的理由:今天库内的驱动错误均为单行,所以这是 finding。第一个包装多行 SDK 错误的驱动撞上 —— 数据库驱动的多行错误(Postgres 的 detail: / hint: 续行、better-sqlite3 包装器)在生态里很常见,#5737 与 #5912 的实测都是照这个形状造的。

修法(同族既定模板,零新词汇)

与前六次完全一致,复用同包 thrown-cause-diagnostics.ts 的 describeThrownForLog:message 保持单行自足,cause 走 meta。按 Logger 契约(packages/spec/src/contracts/logger.ts)warn(message, meta?) 是第二参(不是 error 的第三参 —— warn 没有 Error 槽)。新增字段名不得含 key / token / secret / password 子串(#5573);直接用 helper 自己的 error / issues 即可,无需新字段。

顺带值得判断(不是阻塞项):message 里可否同时补上这条降级的后果(返回 null,调用方会当作「没有这个挂起运行」)—— 目前文本只说「读失败」,没说读失败被翻译成了什么。倾向补,但属于同一处改动的自然范围。

关联

#5912 / PR #6228(本发现的来源,同文件另一方法)、#5737 / PR #5911、#5661、#5636 / PR #5662、#5575 / PR #5639、#5048 / PR #5572、#5660(同在 engine.ts 的 registerDegradedConnector,已闭)、#5573、#4632、#4420、cloud#971。

Activity

  1. added
    domain:servicesRetired 2026-10-22, alias of domain:server (services surface); in-flight cards keep it until landed
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    pm:queueReady for the PM dispatch loop
    and removed
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Aug 7, 2026
  2. self-assigned this
    on Aug 7, 2026
  3. hotlong commented on Aug 7, 2026

    @hotlong
    ContributorAuthor

    晋级 + 认领:维护者 2026-08-07 13:0xZ 对话批复晋级(finding → pm:queue,标签已流转);PM 循环第 8 轮认领。
    会话:session_015a5qkLzpGXhLL2F5gvJ7dD
    分支:claude/issue-6230-degraded-load-log-meta
    Worktree:objectstack-6230
    域:domain:services
    文件面:packages/services/service-automation/src/engine.ts(仅 loadSuspendedRun 降级读取器那一条 logger.warn,行号以内容定位)+ 对应测试

    同族第七次套模板:describeThrownForLog,message 单行自足,cause 走 warn(message, meta?) 第二参(warn 无 Error 槽,与 #5912 的 error 三参不同);⛔ 级别不上调(#4632 判据:功能性降级,warn 正确——上调才是镜像错误);顺带补「读失败被翻译成 null=当作无此挂起运行」的后果句(正文判为同一改动自然范围,采纳)。#5912/PR #6228 已落地,同文件无在飞冲突。


    Generated by Claude Code

  4. hotlong commented on Aug 7, 2026

    @hotlong
    ContributorAuthor

    ACCEPT → PR #6297(draft 转 ready + auto-merge,余下慢检查绿后自动入队)。

    resume 读取路径(严格版 + 降级版)自此双侧全净。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:servicesRetired 2026-10-22, alias of domain:server (services surface); in-flight cards keep it until landedpm:queueReady for the PM dispatch loop

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions