Repository navigation
feat(klaud): status labels, close-to-stop, run-end wakes and clearer outcomes / feat(klaud):状态标签、关闭即停止、运行结束即唤醒与更清晰的结果 - #4484
Conversation
…tcomes - Overlap review indexes only open Klaud PRs; other PRs never block a family. - Remove klaud-abort. A Klaud PR closed unmerged by anyone else stops the candidate, and the planner leaves its family alone for 7 days. - Display-only status labels (klaud-in-progress, klaud-parked, klaud-finished, klaud-infra, klaud-incompatible, klaud-error) set in code with the workflow token so label events start no sweep runs. - Rename outcomes: readiness-blocked -> missing-assets, uncertain -> inconclusive, unexpected-error -> no-outcome, capacity-deferred -> no-capacity, aborted -> closed-by-maintainer. Older records read back under the new names. - missing-assets and weights-missing cool down 7 days instead of 24 hours. - Planner waves every 4 hours. klaud-wake.yml starts a recovery-only pass as soon as a Klaud-owned run ends. - Finishing a maintainer-closed ready PR no longer tries to redraft it.
|
Generally most InferenceX submissions don't need to add changes to the infx package. On the off chance they do, generally the diff is small. Please prompt your agent to clean your PR to keep your diff to only necessary & minimal changes too. We have an increased code quality bar for changes to the infx package. 中文大多数 InferenceX 提交通常不需要修改 infx 包。即使确实需要,改动通常也很小。请让你的 agent 清理 PR,使改动只包含必要且最小的内容。infx 包的改动有更高的代码质量要求。 |
There was a problem hiding this comment.
Beyond the inline findings, I also checked the new workflow_run trigger in .github/workflows/klaud-wake.yml for injection. Event fields only gate the job's if:, and the shell gets nothing from the event. The dispatch is pinned to --ref main, the job is limited to same-repo head repositories, and its token is limited to actions: write, so that path looks safe.
Extended reasoning...
Inline findings are already posted. Separately, the new workflow_run workflow checks out no code and puts no event data into its shell step, so the dangerous-trigger pattern does not apply here.
Findings marked 🟡 are optional suggestions and need no follow-up push.
| now = datetime.now(UTC) | ||
| cutoff = now - timedelta(hours=cooldown_hours) | ||
| window = min(cutoff, now - timedelta(days=INFRA_COOLDOWN_DAYS)) | ||
| since = window - timedelta(hours=MAX_SESSIONS * WAVE_HOURS) | ||
| try: | ||
| runs = github_items( | ||
| repository, |
There was a problem hiding this comment.
🔴 Infra cooldowns can silently drop: the planner may stop seeing 7-day-old missing-assets/weights-missing outcomes and retry those candidates early. The history read now covers about 9-10 days of klaud-plan runs, INFRA_COOLDOWN_DAYS plus MAX_SESSIONS * WAVE_HOURS. It uses a created=>= filter, and GitHub returns at most 1000 runs for that filter. Wake runs are filtered out only after listing (line 246), and the new run-end wakes add many more runs. Fix: list only the runs the cooldown needs, e.g. add event=schedule or a title filter. Otherwise detect the 1000-result cap and warn instead of under-counting.
Why this was flagged
The condition is the number of klaud-plan runs in the lookback, which this checkout cannot count. GitHub's list-workflow-runs endpoint returns at most 1,000 results when created is used. Line 235 lists actions/workflows/klaud-plan.yml/runs?created=>=<since.date()>, and since = now - 7 days - 48h (line 230-231), rounded down to a date, so roughly 9-10 days. Scheduled wakes alone are 48/day (~430-480 runs). Waves add 6/day. The new klaud-wake.yml dispatches one more klaud-plan run for every Klaud-owned e2e or Run Sweep completion, and runs cancelled by the concurrency group still count. The listing comes back newest first, so once the cap is hit the oldest runs are dropped. Their klaud-candidate-* artifacts never reach latest (lines 255-274), and infra holds near the 7-day edge vanish without any warning. On base the window was 24h+36h (~2.5 days, ~150 runs), far under the cap. The WAKE_TITLE check at line 246 runs only after the capped listing, so it does not help.
Verification: In __main__.py:228-231 the lookback becomes 7 days + 48 h, and line 235 rounds it down to a date, giving about 9-10 days. WAKE_TITLE runs are only filtered after listing (line 246), so they count toward GitHub's 1,000-result cap. Past the cap, line 238 catches ValueError and returns {}, set(), or the oldest runs silently drop out.
| # burst of endings collapses into a single recovery pass. Every other run is its own group. | ||
| concurrency: | ||
| group: ${{ inputs.wake && 'klaud-wake' || github.run_id }} |
There was a problem hiding this comment.
🟡 (optional) Recovery passes can now overlap more often, which can push concurrent Klaud sessions past the MAX_CANDIDATES_PER_RUN cap of 5. Only dispatched wakes share the 'klaud-wake' concurrency group. Scheduled :15/:45 wakes and the 4-hourly planner each run in their own github.run_id group. Run-end wakes now arrive at any minute, so they can run recover alongside those passes. Fix: serialize every recovery pass, for example with job-level concurrency: {group: klaud-recover, cancel-in-progress: false} on the recover job. Schedule-only runs keep their own group today.
Why this was flagged
klaud-wake.yml:237 dispatches klaud-plan.yml -f wake=true whenever a Klaud-owned E2E run or klaud/auto-* Run Sweep completes, at any minute. klaud-plan.yml:11 puts only those runs in the 'klaud-wake' group. A scheduled 15,45 * * * * wake or the 0 */4 * * * planner uses github.run_id, so its recover job runs at the same time as a dispatched wake's recover. In lifecycle.py recover(), each pass counts running_sessions() (lifecycle.py:1285). Each then resumes approved sessions until running + resumed >= max_sessions (lifecycle.py:1293). Two overlapping passes both see, say, 4 running sessions, and each grants a different parked candidate, so 6 sessions run against a cap of 5. recovery_lease is per candidate (claims.py:126), so it does not stop this. On the base branch, recovery ran only at :00, :15 and :45, so passes rarely overlapped. The PR text says bursts collapse, but that holds only among dispatched wakes.
Verification: klaud-plan.yml:11 sets group: ${{ inputs.wake && 'klaud-wake' || github.run_id }}, so only dispatched wakes queue behind each other. In lifecycle.py recover(), each pass reads running = len(running_sessions(repository)) once (line 1285) and only counts its own resumed (line 1293). claims.recovery_lease (claims.py:126-155) is per candidate.
| if pull and ( | ||
| pull["state"] != "closed" | ||
| or not pull["draft"] | ||
| or (not pull["draft"] and outcome.outcome != "closed-by-maintainer") |
There was a problem hiding this comment.
🟣 pre-existing, not blocking: Pre-existing, partly fixed: a ready-for-review Klaud PR that a maintainer closes after a cleanup request exists still never finishes. Sessions already stuck this way on main stay stuck. The draft exemption at lifecycle.py:445 applies only to outcome closed-by-maintainer. reconcile reaches that outcome only when no pending cleanup exists (not pending, lifecycle.py:1105). Fix: in the failure branch of verify, ignore the draft flag whenever a closed PR was closed by someone other than Klaud, whatever the pending outcome (for example, call self.closed_by_maintainer(pull) instead of checking the outcome name).
A small fix can ride a push you are already making; otherwise a short reply is enough.
Why this was flagged
The trigger is a closed, non-draft Klaud PR that already has a klaud-cleanup: comment with an outcome other than closed-by-maintainer. On the base branch this state comes from the very bug the PR says it fixes. finish posted the pending unexpected-error request, and then gh pr ready --undo failed on the closed PR. It can also come from a maintainer marking a draft ready and closing it while a finish is in progress. After merge, reconcile reads that pending request as no-outcome and skips the closed_by_maintainer branch because of not pending (lifecycle.py:1105). finish skips the undo (lifecycle.py:544) and the close. Then verify raises "Failure/deferral PR cleanup is incomplete" at lifecycle.py:445, because the PR is not a draft and the outcome is not closed-by-maintainer. Every recovery pass, wakes included, repeats this and reports "needs inspection". The completion report is never posted and the ownership artifact is never deleted, which is the same as on base apart from the error text.
Verification: Pre-existing. Trigger: a closed, non-draft Klaud PR that already has a pending klaud-cleanup: comment whose outcome is not closed-by-maintainer. verify's failure branch raises VerificationError("Failure/deferral PR cleanup is incomplete"), because (not pull["draft"] and outcome.outcome != "closed-by-maintainer") (lifecycle.py:445) is true when the outcome is no-outcome. The session never finishes.
Makes Klaud Cold easier to read and steer from the PR list, and stops it waiting on things that no longer matter.
Changes
klaud/auto-*).--stale-pr-daysis gone.klaud-abortis removed (no PR ever carried it). A Klaud PR closed unmerged by anyone other than Klaud-Cold finishes asclosed-by-maintainer, and the planner leaves that family alone for 7 days.klaud-in-progress,klaud-parked,klaud-finished,klaud-infra,klaud-incompatibleandklaud-error. They are set in code after each session and in every recovery pass, using the workflow token so label events start no Run Sweep runs. Klaud never reads them back, so they are display only. The candidate and recovery jobs getissues: write.readiness-blocked→missing-assets,uncertain→inconclusive,unexpected-error→no-outcome,capacity-deferred→no-capacity,aborted→closed-by-maintainer. Records already on PRs and in artifacts read back under the new names.missing-assetsandweights-missingnow cool down 7 days instead of 24 hours.no-capacitystill retries next wave.klaud-wake.ymldispatches a recovery-only planner pass as soon as a Klaud-owned targeted run or PR sweep ends, instead of waiting for the :15/:45 wake (which ran 15 to 45 minutes late). Wakes share one concurrency group, so bursts collapse. The scheduled wakes stay as a backstop.gh pr ready --undo, which fails on closed PRs and left the session stuck.Validation
infx/tests/klaudandinfx/tests/workflows: 881 passed. ruff, actionlint and zizmor clean.no-outcomeshare falling.After merge
klaud-abort; they will be edited to say "close this PR".中文
让 Klaud Cold 更易于从 PR 列表查看和控制,并且不再等待已无关的事项。
klaud/auto-*),移除--stale-pr-days。klaud-abort(从未有 PR 使用过)。由 Klaud-Cold 以外的人关闭且未合并的 Klaud PR 以closed-by-maintainer结束,规划器 7 天内不再处理该配置族。klaud-in-progress、klaud-parked、klaud-finished、klaud-infra、klaud-incompatible、klaud-error,由代码在每个会话结束后和每次恢复时设置,使用工作流令牌,因此不会触发 Run Sweep。Klaud 从不读取它们,仅用于展示。readiness-blocked→missing-assets,uncertain→inconclusive,unexpected-error→no-outcome,capacity-deferred→no-capacity,aborted→closed-by-maintainer。已有记录按新名称读取。missing-assets与weights-missing冷却 7 天(原为 24 小时);no-capacity仍在下一轮重试。klaud-wake.yml:Klaud 自有的定向运行或 PR sweep 结束时立即触发仅恢复的规划运行;定时唤醒保留为兜底。gh pr ready --undo。AI model disclosure
Prepared with Claude Code using
anthropic/claude-opus-5-5, including a read-only review agent on the same model.