Skip to content

fix(eval): 可观测性链路把用户取消的运行记成 cancelled,不再记成任务失败 - #255

Open
wxrbyte wants to merge 1 commit into
helsome:mainfrom
wxrbyte:codex/fix-observability-cancelled-status
Open

wxrbyte wants to merge 1 commit into
helsome:mainfrom
wxrbyte:codex/fix-observability-cancelled-status

Conversation

@wxrbyte

@wxrbyte wxrbyte commented Sep 29, 2026

Copy link
Copy Markdown
Contributor

问题与修复

kernelHost.observeRunEvent() 把 run_completed / run_failed 先压成一个布尔量,settleEvaluationRun() 便只能落 completed / failed 两种状态:

const completed = event.type === 'run_completed';
void this.settleEvaluationRun(event.runId, event.sessionId, completed, event.timestamp);
// ...
const status: EvaluationRunStatus = completed ? 'completed' : 'failed';

而用户主动取消恰恰是以 run_failed + error.code = 'RUN_CANCELLED' 落地的(packages/shared/src/kernel/run-manager.ts:419-421,stream-event-adapter.ts:90-98 再把它归一成流事件的 cancelled),于是「取消」在这条链路上被记成了「任务失败」。

后果在统计口径上可见 —— packages/shared/src/evaluation/aggregate.ts:182-183:

export function isSkippedRun(run: EvaluationRun): boolean {
  return run.status === 'cancelled' || run.status === 'skipped';
}

记成 failed 之后 isSkippedRun 为 false ⇒ isQualityRun 为 true(运行已 started)⇒ 取消的运行被计入 evaluated 而 skipped 恒为 0;aggregate.ts:69-71 的 verdictForRun() 还会把该 case 判成 'fail'。core/src/evaluation.ts:624 对 skipped 的注释正是预期口径:「Cases never run: cancelled mid-flight or skipped after an abort.」

同层另一个生产者 experiment-service.ts:911 早已按 RUN_CANCELLED 落 'cancelled',本改动让两个生产者对 EvaluationRunStatus 的口径一致。

修法(kernelHost.ts,+20/-4):

  • observeRunEvent() 直接推导 EvaluationRunStatus:run_completed → completed;run_failed + RUN_CANCELLED → cancelled;其余 run_failed → failed;
  • settleEvaluationRun() 形参 completed: boolean → status: EvaluationRunStatus;EvaluationRun 字面量本就是 status 简写无需改动,exportAgentTrace({ completed }) 改为 completed: status === 'completed',取值与修复前等价,trace 侧行为不变。

预算 / 护栏终止(BUDGET_EXHAUSTED、RETRY_STORM、LOOP_DETECTED)仍然是 failed,未被一并归入 cancelled —— 本 PR 只修「用户主动取消」这一条,并有一条锁定用例守住这条边界。

Closes #254

可复现测试报告

环境:Windows 11 + Git Bash,bun 1.3.14;上游 main ba5dcdfd。

新增两条用例(apps/electron/src/main/kernelHost.test.ts):通过 runs.subscribe 注入 run_started → run_failed{RUN_CANCELLED} 并断言落到 EvaluationStore.addRun 的状态;另一条断言普通运行时错误(PROVIDER_ERROR)仍是 failed。

命令:bun test apps/electron/src/main/kernelHost.test.ts --isolate

状态 结果
修复前 12 pass / 1 fail
修复后 13 pass / 0 fail

修复前的失败输出(即缺陷证据):

error: expect(received).toMatchObject(expected)

  {
    "id": "r1",
-   "status": "cancelled",
+   "status": "failed",
  }

其余门禁:

  • bun test apps/electron --isolate = 32 pass / 0 fail(5 个文件);
  • bun run typecheck = core / i18n / shared / ui / electron 五个工作区全部 exit 0;
  • 本机 packages/shared --isolate 基线为 1111 pass / 3 fail(ExperimentService.runExperiment 取不到 gitSha、durable research recovery 的 2s 轮询预算、langfuse backend 依赖本机 Langfuse 连通性),三项均在本 PR 未触碰的 packages/shared 上、与改动无关。

未运行:完整 e2e 套件(本机沙箱无法 spawn cmd.exe,属已知环境限制,详见此前 issue #223)。

文件级重叠披露

apps/electron/src/main/kernelHost.ts 是一个高争用文件,当前有 7 个 open PR 触及它,逐一核对后确认与本 PR 改动块不相交(坐标以 main ba5dcdfd 为准):

PR 作者 在该文件的 hunk 起点 与本 PR 的关系
#64 yjg-djb 182 / 907 / 1339 同一函数:改的是 EvaluationRun 字面量的 error: 字段(≈1363-1370);本 PR 改 :1335-1346(状态推导)与 :1400(completed 实参),相隔 ≥20 行,且 #64 未触碰任何状态推导逻辑
#244 wxrbyte 2097 ≥750 行之外
#246 wxrbyte 1579 / 2461 ≥230 行之外
#97 Jackkp0t 271 / 2332 ≥230 行之外
#56 Cordis798 多处,最近 2313 ≥900 行之外
#4 eric7h 164 / 289 / 2039 ≥650 行之外
#53 CC1227871(Draft,base 6a9a288e) 8 / 491 / 2610 ≥1200 行之外

最接近的 #64(issue #19 的 redaction 分支)当前 mergeable_state=clean,其 base 为陈旧的 6405b7b9。若 #64 先合入,本分支需按常规 rebase;两者语义独立(一个是错误文本脱敏,一个是运行状态分类),不会互相覆盖。

UI 变化

无可见 UI 变化(只改主进程写入评估存储的状态字段;不改渲染层、不改 IPC 面)。

kernelHost.observeRunEvent() 把 run_completed / run_failed 先压成一个布尔量,
settleEvaluationRun() 便只能落 completed / failed 两种状态。而用户主动取消恰恰
是以 run_failed + error.code = 'RUN_CANCELLED' 落地的(RunManager 的取消分支,
stream-event-adapter 再把它归一成流事件的 cancelled),于是「取消」在这条可观测性
链路上被记成了「任务失败」。

后果在统计口径上可见:packages/shared/src/evaluation/aggregate.ts 的
isSkippedRun() 以 status === 'cancelled' 判定跳过,记成 failed 之后
isQualityRun() 反而为 true ⇒ 取消的运行被计入 evaluated、skipped 恒为 0,
verdictForRun() 还会把该 case 判成 'fail'。core 的 ExperimentExecutionCounts
对 skipped 的注释写得很清楚:「Cases never run: cancelled mid-flight or skipped
after an abort.」

同层的另一个生产者 experiment-service 早已按 RUN_CANCELLED 落 'cancelled'
(:911),本改动让两个生产者对 EvaluationRunStatus 的口径一致。

修法:observeRunEvent() 直接推导 EvaluationRunStatus;settleEvaluationRun() 的形参
由 completed: boolean 换成 status: EvaluationRunStatus;EvaluationRun 字面量本就是
status 简写无需改动,exportAgentTrace({ completed }) 改写为
completed: status === 'completed',取值与修复前等价,trace 侧行为不变。预算/护栏终止
(BUDGET_EXHAUSTED 等)仍记 failed,未被一并归入 cancelled。

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant