fix(repair): single status-check rollup; stale failures no longer block finalization - #1109
Conversation
|
🦞👀 Pull request received. I will update this pull request when review starts. |
|
Codex review: needs maintainer review before merge. Reviewed August 10, 2026, 4:17 PM ET / 20:17 UTC. ClawSweeper reviewWhat this changesThis PR centralizes repair status-check selection so newer runs replace stale failures in finalization, comment routing, and post-flight checks. Merge readinessKeep open: current main still evaluates every finalizer check run, while this PR selects the latest normalized run and supplies sufficient production-boundary proof. Likely related people: Peter Steinberger (high) and Dwin Gharibi (medium). Priority: P2 Review scores
Verification
How this fits togetherRepair automation reads GitHub pull-request checks before publishing repair results or deciding whether a repair can progress. The shared rollup now feeds the finalizer, comment router, and post-flight gate while each retains its own blocker policy. flowchart LR
A[GitHub check runs] --> B[Shared status-check rollup]
B --> C[Latest normalized run]
C --> D[Comment router]
C --> E[Post-flight gate]
C --> F[Report-only finalizer]
F --> G[Published repair queue]
Before merge
Agent review detailsSecurityNone. Review metrics
Merge-risk optionsMaintainer options:
Copy recommended automerge instructionTechnical reviewBest possible solution: Keep one case-normalized rollup as the shared selection owner, preserve the committed production-boundary proof, and land after the current required checks complete. Do we have a high-confidence way to reproduce the issue? Yes—source-reproducible with high confidence. Current main retains the raw-check finalizer path, and the committed real-GitHub payload demonstrates the stale-blocker result before the fix and the cleared result after it. Is this the best way to solve the issue? Yes. Extracting the already-similar selection rules into one tested helper is the narrowest maintainable repair while preserving each caller's blocker policy. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 2b5b345063ef. LabelsLabel changes:
Label justifications:
EvidenceWhat I checked:
Likely related people:
Rating scale
Overall follows the weaker of proof and patch quality. Workflow
HistoryReview history (3 earlier review cycles) |
|
/review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
Summary
Unify the three repair-lane interpretations of GitHub's
statusCheckRollupbehindsrc/repair/status-check-rollup.ts. The shared owner now selects the latest check per case-normalized workflow/name, parses and matches ignored names case-insensitively, and applies one timestamp precedence (started*, thencreated*, thencompleted*, with an untimestamped pending rerun kept visible).Comment-router, post-flight, and finalizer keep their lane-specific blocker policy. The intended finalizer bug fix is explicit: once a newer run of a check succeeds, an older failed run of the same check no longer creates a stale
needs_checksblocker.Drift re-verification
Re-verified on fresh
origin/mainate13791786799f6a51a39806938847bbc48948e5e:comment-router-utils.tsdeduplicated case-insensitive workflow/name identities, but preferred completion timestamps before start timestamps.post-flight.tsdeduplicated the same identities, preferred start/creation timestamps before completion timestamps, and treated an untimestamped pending rerun as newest.finalize-open-prs.tsdid not deduplicate at all and matched ignored names case-sensitively.The new module owns those drift-prone mechanics once. Representative router and post-flight policy fixtures pin their unchanged decisions; dedicated unit cases pin casing, timestamp-field precedence, and untimestamped pending behavior.
Finalizer liveness trace
Verdict: live.
The dedicated
repair-finalize-open-prs.ymlworkflow was deleted in #964, but the report path remains live. Every completedrepair cluster workerrun triggers.github/workflows/repair-publish-results.yml(and the workflow also supports manual dispatch). After building repair code and downloading a publishable worker artifact, itsPublish result ledgerstep runs:That package script executes
dist/repair/finalize-open-prs.js. In this live--write-reportmode, module initialization invokesloadPublishedRecords,listOpenPullRequests,hydratePullRequest(fetchPullRequestViewplusfetchReviewThreadState, with mergeability refresh when needed), andclassifyPullRequest. Classification invokes the buggysummarizeChecks; its result feeds the PR'schecks,needs_checksblocker, recommended next action, and overallsummarizereport.writeReportsthen emitsresults/finalize-open-prs.jsonand.md, and the followingrepair:publish-resultinvocation reads that JSON into the published Open PR Finalizer Queue.The removed workflow's dispatch path is not invoked by this live command:
--write-reportleavesdispatchRepairsfalse, soselectDispatchCandidates,executeDispatches, anddispatchRepairdo not run. They remain reachable only when the still-exposed CLI is explicitly called with dispatch/execute flags.Behavior proof
Committed proof:
docs/proof/status-check-rollup/packages the real-GitHub classification pair, redacted 26-entry payload, report-only finalizer output, runnable harness, local provenance, and Docker-backed Crabbox provenance. The inline summary below remains the concise result.The regression test was added before the production fix. Against fresh
origin/main, an older failure plus a newer success of the sameCI / unitcheck produced this red run verbatim:At committed head
10d0801364df699b697fad5cea1ba59a52b91cae, the exact same regression is green (verbatim):Focused repair proof: 62 passed, 0 failed, covering the finalizer regression, router and post-flight equivalence fixtures, ignored-name casing, timestamp precedence, and untimestamped pending reruns.
Validation
pnpm run build:all— passedpnpm run test:no-build— passed (3,282 tests; 3,273 passed, 9 skipped, 0 failed)pnpm run lint— passedpnpm run format:check— passedpnpm run check:active-surface— passedpnpm run check:dashboard-queue-boundary— passedpnpm run check:limits— passedorigin/main— clean, no accepted/actionable findingsDocker container full-suite proof
Environment: Crabbox
provider=local-container, imagenode:24-bookworm, Node 24.19.0, Git 2.39.5, pnpm 11.10.0 through Corepack. A checksum-verified static jq 1.8.1 was installed into$HOME/.local/binin both fresh containers:Current
origin/mainbaseline ate13791786799f6a51a39806938847bbc48948e5e, leasecbx_72566c5500ea: exactly the three partial-clone blob-hydration tests were selected. Verbatim test output:PR head
10d0801364df699b697fad5cea1ba59a52b91cae, full-suite leasecbx_f215cbbe1560: 3,282 tests, 3,271 passed, 8 skipped, and exactly the same three failures. There were zerojq: command not foundoccurrences. Verbatim full-run tail:The three failures are environmental in the bare container. Its Debian Bookworm Git 2.39.5 does not return an
<object-id> missingbatch row for the fixture'sGIT_NO_LAZY_FETCH=1 git cat-file --batch-checkprobe. It instead attempts a promisor lazy fetch, emitsfatal: could not fetch <object-id> from promisor remote, and returns no batch row, sohydratePullRequestReviewBlobsfails closed before its bounded explicit fetch. These tests should be capability/env-gated in a follow-up; this PR does not change them.Container delta versus current
main: zero failures.Real
ghproduction-boundary traceThis proof used the authenticated GitHub CLI 2.97.0 against the real GitHub API, not the regression test's fake executable. The live subject was open PR #1099 at head
1aa53a6a09e543e4a6e4906f7e3cc0bf34a4bd65. Its case-normalizedClawSweeper Dispatch / dispatchidentity contains older cancelled runs, a later successful run, and later acceptable skipped reruns. URLs are redacted; Actions run and job IDs are retained.[ {"workflowName":"ClawSweeper Dispatch","name":"dispatch","status":"COMPLETED","conclusion":"CANCELLED","startedAt":"2026-08-10T15:46:40Z","completedAt":"2026-08-10T15:46:41Z","actionsRunId":"31405430931","jobId":"93510453087"}, {"workflowName":"ClawSweeper Dispatch","name":"dispatch","status":"COMPLETED","conclusion":"CANCELLED","startedAt":"2026-08-10T15:46:42Z","completedAt":"2026-08-10T15:46:43Z","actionsRunId":"31405432821","jobId":"93510462224"}, {"workflowName":"ClawSweeper Dispatch","name":"dispatch","status":"COMPLETED","conclusion":"SKIPPED","startedAt":"2026-08-10T15:51:27Z","completedAt":"2026-08-10T15:51:27Z","actionsRunId":"31405849078","jobId":"93511873241"}, {"workflowName":"ClawSweeper Dispatch","name":"dispatch","status":"COMPLETED","conclusion":"SKIPPED","startedAt":"2026-08-10T15:51:27Z","completedAt":"2026-08-10T15:51:27Z","actionsRunId":"31405849118","jobId":"93511874745"}, {"workflowName":"ClawSweeper Dispatch","name":"dispatch","status":"COMPLETED","conclusion":"SKIPPED","startedAt":"2026-08-10T15:51:28Z","completedAt":"2026-08-10T15:51:27Z","actionsRunId":"31405850201","jobId":"93511876958"}, {"workflowName":"ClawSweeper Dispatch","name":"dispatch","status":"COMPLETED","conclusion":"SKIPPED","startedAt":"2026-08-10T15:51:39Z","completedAt":"2026-08-10T15:51:28Z","actionsRunId":"31405851663","jobId":"93511932492"}, {"workflowName":"ClawSweeper Dispatch","name":"dispatch","status":"COMPLETED","conclusion":"SUCCESS","startedAt":"2026-08-10T15:46:46Z","completedAt":"2026-08-10T15:46:55Z","actionsRunId":"31405434030","jobId":"93510469023"} ]The production report-only entry point was run at this PR head with an exact branch prefix:
For the before/after contrast, the exact pre-change
summarizeCheckslogic from parente13791786799f6a51a39806938847bbc48948e5ewas executed as a local one-off harness, not committed, beside head10d0801364df699b697fad5cea1ba59a52b91cae. Both classifications consumed the same real 26-entrystatusCheckRolluppayload; the relevant repeated identity is listed above. Verbatim redacted pair:{ "oldPreChangeCommit": "e13791786799f6a51a39806938847bbc48948e5e", "old": { "total": 26, "counts": {"CANCELLED":3,"SKIPPED":6,"SUCCESS":17}, "blockers": [ "ClawSweeper Dispatch / dispatch:CANCELLED", "ClawSweeper Dispatch / dispatch:CANCELLED" ], "needs_checks": "needs_checks:ClawSweeper Dispatch / dispatch:CANCELLED; ClawSweeper Dispatch / dispatch:CANCELLED" }, "newHeadCommit": "10d0801364df699b697fad5cea1ba59a52b91cae", "new": { "total": 14, "counts": {"SKIPPED":2,"SUCCESS":12}, "blockers": [], "needs_checks": null } }Automation-risk disposition
This behavior change means a stale failed run of a re-run check no longer vetoes finalization after a newer run of the same case-normalized workflow/check identity establishes the current acceptable state. That is the correct GitHub check semantics: the newest run for an identity is current, and it is the same rule already applied by the router and post-flight lanes. The blast radius is limited to finalizer check classifications (
checks,needs_checks, recommended action, and summary) that feed the published Open PR Finalizer Queue; current failures and pending reruns still block, and no report schema, dispatch policy, or queue action changed. If this interpretation proves unsafe, reverting10d0801364df699b697fad5cea1ba59a52b91caerestores the former per-entry processing and stale-failure veto.OpenClaw Bay
No Bay change is needed. This is a repair-only interpretation fix; it changes no Worker contract, report schema, queue/lifecycle contract, dashboard code, or observer/action boundary. The existing finalizer report contains corrected check counts and blockers under the same shape.