Skip to content

fix(klaud): verify older and aggregate baselines, free abandoned work / fix(klaud): 验证旧版与聚合基线并释放放弃的工作 - #4137

Merged
adibarra merged 4 commits into
mainfrom
fix/klaud-baseline-preflight
Oct 16, 2026
Merged

adibarra merged 4 commits into
mainfrom
fix/klaud-baseline-preflight

Conversation

@adibarra

@adibarra adibarra commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes found while monitoring the first planner run after #4118 (run 38802283618). That run approved 26 candidates and launched only 4: 22 were deferred at the baseline step, and selection.json didn't say why. One of the 4 launched candidates then failed before the agent started.

Problem Evidence Fix
Producers from before the generator's --no-evals flag exit with unrecognized arguments: --no-evals, so their published baselines can never be verified 9 multi-node DSR1 Dynamo families (dsr1-fp8-b200-dynamo-trt, dsr1-fp8-h200-dynamo-trt, dsr1-fp4-gb200-dynamo-trt, …), producers from March producer_matrix retries without the flag when the producer rejects it and keeps only throughput rows.
Aggregate multi-node rows never match their producer entry 9 agentic agg families with "No verified public baseline points" The app mirrors an aggregate engine into both role columns (InferenceX-app packages/db/src/etl/topology.ts), so it publishes decode_num_workers equal to prefill's while the matrix has 0. public_point now mirrors prefill for aggregates. Older result writers also published a one-worker aggregate as a single-node row (the 2026-09-18 dsv4-*-dynamo-sglang-agentic-agg producer did); _matches_public_point accepts that encoding only for one-worker aggregates.
Baseline deferrals discard their error 22 deferrals with no reason in the artifact or log selection.json gains baseline-deferral-reasons, and the step summary counts each reason. Verification errors keep their fixed public message, subprocess errors record only their type, and other errors record their type plus up to 300 characters.
The Claude CLI install occasionally leaves no native binary ("claude native binary not installed") Candidate e920fad… in that run; 1 of the last 15 runs Both Klaud workflows reinstall once before failing.
An attempt that fails before the agent starts is cooled down for 24 hours The same candidate would wait a day over a download flake An attempt whose diagnostics record action-outcome: skipped is retried next wave, like a capacity deferral. Attempts where the agent ran keep their cooldown.

Abandoned work

Sessions that hit the 6h ubuntu-latest job limit (14 of the last 55) left draft PRs, klaud/auto-* branches and family claims behind. Recovery couldn't clean them up, because its targeted-run listing for older parents exceeded GitHub's 1000-result cap ("Incomplete GitHub listing"). The families stayed blocked: 40 of 42 claim refs belonged to finished parents.

Recovery now sweeps before selection. A candidate is abandoned once no planner session (Run candidate <id> job) and no final sweep (run-sweep.yml on its branch) is active. For an abandoned candidate it:

  • cancels the candidate's leftover smoke runs (e2e-tests.yml started by Klaud; nothing reads them once the session is gone);
  • comments on and closes its draft PR, but only if every commit is Klaud's and the PR has no klaud-handoff label, then deletes the branch;
  • deletes leftover branches whose latest PR Klaud itself closed (PRs a maintainer closed are kept);
  • releases claims held by finished parents.
  • deletes klaud/recovery-* lease refs whose parent run finished, when no other planner is still reconciling. These refs stop two recovery passes from cleaning up the same session at once; none were ever deleted, so 568 had piled up.

Families blocked by recovery stay blocked only while their candidate is still active. Recovery's targeted-run listing is now limited to the parent's lifetime. A listing error skips the sweep for that wave instead of failing the planner.

Dry run against the live repo, with no writes: 11 abandoned drafts closed (#4081, #4082, #4091, #4092, #4093, #4095, #4096, #4098, #4120, #4122, #4124); 9 orphaned smoke runs cancelled, some of them queued since 04:35 UTC; 43 klaud/auto-* branches deleted, the stale claims released and 402 recovery leases deleted; recovery-blocked families down to 3 active ones. #4115 is kept because its final sweep is still running, and ready PRs are left for review.

Replaying all 22 deferred candidates locally with their real producers: 17 now verify (previously 0). The remaining 5 are expected or not Klaud bugs:

Family Reason
glm5.2-fp4-b200-sglang-agentic-mtp, dsv4-fp4-b200-vllm-agentic-mtp TOTAL_CPU_DRAM_GB recipe mismatch after #4125 (intentional)
dsr1-fp8-gb200-dynamo-sglang 3 frozen baseline points the current family no longer generates
kimik3-fp4-h200-vllm-agentic-balanced 25 of its rows come from run 30781313910, which is missing from workflow-info, so their producer can't be proven
中文

本 PR 修复了在 #4118 之后首次规划运行(run 38802283618)中发现的问题:26 个候选通过评审,却只启动了 4 个,其余 22 个在基线步骤被推迟,selection.json 也没有记录原因;启动的 4 个候选中又有 1 个在 Agent 启动前失败。

  • 旧 producer 拒绝 --no-evals: 早于该参数的 producer 会以 unrecognized arguments: --no-evals 退出,其已发布基线永远无法验证;9 个多节点 DSR1 Dynamo 配置族受影响(producer 来自 3 月)。producer_matrix 现在会在 producer 拒绝该参数时不带参数重试,并只保留吞吐量行。

  • 聚合多节点行无法匹配 producer 条目: 9 个 agentic agg 配置族报 "No verified public baseline points"。应用会把聚合引擎镜像到两个角色列(InferenceX-app 的 packages/db/src/etl/topology.ts),因此发布的 decode_num_workers 与 prefill 相同,而矩阵中为 0。public_point 现在对聚合配置镜像 prefill。较旧的结果写入器还会把单 worker 聚合发布为单节点行(2026-09-18 的 dsv4-*-dynamo-sglang-agentic-agg producer 即如此);_matches_public_point 仅对单 worker 聚合接受这种编码。

  • 基线推迟丢弃错误: selection.json 新增 baseline-deferral-reasons,步骤摘要按原因计数。验证错误保留其固定的公开消息,子进程错误只记录类型,其他错误记录类型和最多 300 个字符。

  • Claude CLI 安装偶发失败: 安装有时缺少原生二进制("claude native binary not installed"),该运行中的候选 e920fad… 即如此,最近 15 次运行中出现 1 次。两个 Klaud 工作流现在会在失败前重装一次。

  • Agent 未启动的尝试被冷却 24 小时: diagnostics 记录 action-outcome: skipped 的尝试现在会在下一轮重试,与容量推迟相同;Agent 已运行的尝试保持原有冷却。

  • 放弃的工作: 达到 6 小时 ubuntu-latest 作业上限的会话(最近 55 次中有 14 次)会遗留草稿 PR、klaud/auto-* 分支和配置族声明;恢复流程对较旧父运行的定向运行列表超过 GitHub 1000 条上限("Incomplete GitHub listing"),无法收尾,配置族一直被阻塞(42 个声明中有 40 个属于已结束的父运行)。现在恢复流程会在选择前清扫:当没有规划会话(Run candidate <id> 作业)和最终 sweep(其分支上的 run-sweep.yml)在运行时,候选视为已放弃;取消其残留的冒烟运行,关闭只有 Klaud 提交且没有 klaud-handoff 标签的草稿 PR 并删除分支,删除 Klaud 自己关闭的 PR 遗留的分支(维护者关闭的保留),并释放已结束父运行的声明。当没有其他规划运行仍在收尾时,还会删除父运行已结束的 klaud/recovery-* 租约引用(此前从未删除,已累积 568 个)。恢复阻塞的配置族只在候选仍活动时保持阻塞。定向运行列表限定在父运行的存续期内;列表错误只会跳过本轮清扫。对线上仓库的只读演练:关闭 11 个放弃的草稿 PR,取消 9 个孤立的冒烟运行,删除 43 个 klaud/auto-* 分支并释放陈旧声明,恢复阻塞的配置族降至 3 个。

用真实 producer 在本地重放全部 22 个被推迟的候选:现在有 17 个通过验证(此前为 0)。其余 5 个属于预期情况或并非 Klaud 缺陷:两个是 #4125 之后预期的 TOTAL_CPU_DRAM_GB 配方不匹配;dsr1-fp8-gb200-dynamo-sglang 有 3 个当前配置族不再生成的冻结基线点;kimik3-fp4-h200-vllm-agentic-balanced 有 25 行来自 workflow-info 中缺失的 run 30781313910,无法证明其 producer。

AI model disclosure

Prepared with anthropic/claude-opus-5-5, which investigated the planner run, made the changes and tests, and drafted this description. No delegated agents were used.

…urvive install flakes

- Producers older than the generator's --no-evals flag rejected it, so their
  baselines could never be verified and nine multi-node families were deferred
  every wave; retry without the flag and keep only throughput rows.
- select now records why each candidate's baseline was deferred in
  selection.json and the step summary instead of discarding the error.
- Both Klaud workflows retry the Claude CLI install once when the native
  binary is missing.
- An attempt whose agent step never ran is retried next wave instead of
  cooling down for 24 hours.

- 早于生成器 --no-evals 参数的 producer 会拒绝该参数,导致其基线无法验证,
  九个多节点配置族每一轮都被推迟;现在会不带该参数重试,并只保留吞吐量行。
- select 现在会在 selection.json 和步骤摘要中记录每个候选基线被推迟的原因,
  而不是丢弃错误。
- 两个 Klaud 工作流在缺少原生二进制时会重试一次 Claude CLI 安装。
- Agent 步骤从未运行的尝试会在下一轮重试,而不是冷却 24 小时。
…them

The app mirrors an aggregate engine into both role columns, so a one-engine
aggregate row publishes decode_num_workers equal to prefill's, while the matrix
gives decode zero workers. Older result writers also published a one-worker
aggregate as a single-node row. Either way no public row matched its producer
entry, deferring nine agentic agg families with "No verified public baseline
points".

应用会把聚合引擎镜像到 prefill 和 decode 两列,因此聚合行发布的
decode_num_workers 与 prefill 相同,而矩阵中 decode 为零个 worker;较旧的结果
写入器还会把单 worker 聚合发布为单节点行。两种情况下公开行都无法匹配其
producer 条目,导致九个 agentic agg 配置族因"No verified public baseline
points"被推迟。
@adibarra adibarra changed the title fix(klaud): regenerate older baselines and record deferral reasons / fix(klaud): 重新生成旧基线并记录推迟原因 fix(klaud): verify older and aggregate baselines, record deferral reasons / fix(klaud): 验证旧版与聚合基线并记录推迟原因 Oct 15, 2026
Sessions that hit the 6h job limit left draft PRs, klaud/auto branches and
family claims behind, and recovery could not reconcile them, so their
families stayed blocked indefinitely. Before selection, recovery now treats
a candidate as abandoned once no planner session or final sweep is active:
it cancels the candidate's leftover smoke runs, closes a draft PR that only
Klaud committed to, deletes leftover branches of PRs Klaud itself closed and
releases claims held by finished parents. Handoff, maintainer-touched and
ready PRs are left alone. Recovery also bounds its targeted-run listing to
the parent's lifetime, which exceeded GitHub's 1000-result cap for older
parents.

达到 6 小时作业上限的会话会遗留草稿 PR、klaud/auto 分支和配置族声明,且恢复
流程无法收尾,导致这些配置族被无限期阻塞。现在恢复流程会在选择前,将没有活动
规划会话或最终 sweep 的候选视为已放弃:取消其残留的冒烟运行,关闭只有 Klaud
提交过的草稿 PR,删除 Klaud 自己关闭的 PR 遗留的分支,并释放已结束父运行持有
的声明。交接、维护者改动过或已就绪的 PR 保持不变。恢复流程还会将定向运行列表
限定在父运行的存续期内,此前较旧父运行的列表超过了 GitHub 1000 条结果上限。
@adibarra adibarra changed the title fix(klaud): verify older and aggregate baselines, record deferral reasons / fix(klaud): 验证旧版与聚合基线并记录推迟原因 fix(klaud): verify older and aggregate baselines, free abandoned work / fix(klaud): 验证旧版与聚合基线并释放放弃的工作 Oct 15, 2026
…k sweep

Each recovery pass leaves a klaud/recovery-<parent>-<candidate> ref behind and
none was ever deleted (568 today). When no other planner is still reconciling,
the sweep now deletes leases whose parent run finished and whose candidate is
not active; a later pass recreates any it still needs.

每次恢复都会留下 klaud/recovery-<父运行>-<候选> 引用,且从未删除(目前有 568 个)。
当没有其他规划运行仍在收尾时,清扫现在会删除父运行已结束且候选不再活动的租约;
之后的恢复如仍需要会重新创建。
@claude

claude Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @adibarra's task in 1m 14s —— View job


LGTM - no blocking issues found

adibarra added a commit that referenced this pull request Oct 16, 2026
# Conflicts:
#	inferencex-e2e/infx/klaud/lifecycle.py
#	inferencex-e2e/infx/tests/klaud/test_klaud_github.py
adibarra added a commit that referenced this pull request Oct 16, 2026
# Conflicts:
#	inferencex-e2e/infx/klaud/lifecycle.py
#	inferencex-e2e/infx/tests/klaud/test_klaud_github.py
adibarra added a commit that referenced this pull request Oct 16, 2026
# Conflicts:
#	inferencex-e2e/infx/klaud/lifecycle.py
#	inferencex-e2e/infx/tests/klaud/test_klaud_github.py

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings marked 🟡 are optional suggestions and need no follow-up push.

Comment on lines +654 to +667
for pull in pulls:
match = re.fullmatch(rf"klaud/auto-({CANDIDATE_ID})", pull["head"]["ref"])
if not match or match[1] in live:
continue
if (
pull["draft"]
and pull["user"]["login"] == BOT
and (pull["head"]["repo"] or {}).get("full_name") == repository
and not any(label["name"] == "klaud-handoff" for label in pull["labels"])
and bot_committed(
github.items(repository, f"pulls/{pull['number']}/commits?per_page=100")
)
):
abandoned.append(pull)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Klaud draft PRs a maintainer has re-swept, or whose recovery hit a transient error, now get closed and their branch deleted in the same recovery pass. The base left them open. sweep_abandoned only checks author, draft, labels and commit authorship. It ignores that reconcile() failed for this candidate moments earlier, for example with "New maintainer runs require explicit handoff". Fix: have recover() pass sweep_abandoned the ids whose reconcile raised anything but PendingCleanup, and skip their PR close, branch delete and claim release. Orphaned-smoke sessions still get freed.

Why this was flagged

Trigger: a session hits the 6h job limit and leaves a draft Klaud PR with a sweep label. A maintainer then starts a run-sweep.yml run on it with no commit. When that run ends, the next recover() calls reconcile(). Session.runs() raises VerificationError("New maintainer runs require explicit handoff") at lifecycle.py:136-140. The except at lifecycle.py:567-574 only adds the family to blocked, not the id to live. sweep_abandoned (lifecycle.py:654-667) then finds a draft, Klaud-authored PR with only github-actions[bot] commits and no klaud-handoff label. It comments "abandoned", closes the PR and deletes the branch (lifecycle.py:691-706), and releases the family claim (lifecycle.py:676-681). On the base branch the PR, branch and claim stayed intact and the family stayed blocked until a maintainer added a handoff label. The owned_activity() liveness check only covers run-sweep runs that are still active, so it does not prevent any of this.

Verification: Session.runs() raises VerificationError("New maintainer runs require explicit handoff") at lifecycle.py:136-140. The except at lifecycle.py:567-574 only does blocked.add(candidate.family). Unlike the lease and non-terminal branches (lines 557 and 563), it never adds candidate.id to live. The PR gets the "abandoned" comment, is PATCHed to closed, and its branch is DELETEd.

for attempt in 1 2; do
rm -rf "$RUNNER_TEMP/claude-code"
npm install --prefix "$RUNNER_TEMP/claude-code" --no-audit --no-fund @anthropic-ai/claude-code@2.1.282 &&
[ "$("$claude_cli" --version 2>&1)" = "2.1.282 (Claude Code)" ] && break

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 (optional) If claude --version ever writes to stderr, both Klaud workflows now fail the install step twice and no candidate starts. The base ignored stderr there. The new check merges stderr into the compared string with 2>&1, so any warning breaks the exact match against "2.1.282 (Claude Code)". Fix: compare only stdout, which the base already did, and send stderr to the log, e.g. [ "$("$claude_cli" --version)" = "2.1.282 (Claude Code)" ]. Apply this at both sites (klaud-candidate.yml:85, klaud-plan.yml:142).

Why this was flagged

This finding only applies if the pinned 2.1.282 CLI, its npm wrapper or Node prints anything to stderr on a successful --version, such as a deprecation, ExperimentalWarning or updater notice. The new loop at klaud-candidate.yml:85 and klaud-plan.yml:142 compares $("$claude_cli" --version 2>&1) with the exact string "2.1.282 (Claude Code)". Any stderr text makes the comparison fail, so both attempts fail and [ "$attempt" = 2 ] && exit 1 aborts the step. The base used test "$("$claude_cli" --version)" = ... and compared stdout only, so a working install with a stderr warning passed there. After this change, the claude step is skipped for every candidate, and the planner's overlap-review step is skipped too. The retry does not help because it repeats the same comparison.

Verification: In both klaud-candidate.yml:85 and klaud-plan.yml:142 the check is now [ "$("$claude_cli" --version 2>&1)" = "2.1.282 (Claude Code)" ] && break, which folds stderr into the string being compared. The base line captured only stdout. With any stderr text, both attempts fail, [ "$attempt" = 2 ] && exit 1 aborts the install step, and no candidate or planner session starts.

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

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant