ci: hourly sweeper re-fires dropped pull_request CI runs#110889
Conversation
Fresh PRs race GitHub's merge-ref computation: the open-event CI run can drop entirely or be created as an un-rerunnable startup_failure (~10-16 runs daily). The sweeper lists recently updated open PRs hourly, finds heads whose only pull_request-event CI runs are startup failures (or missing), and re-fires the event by close/reopen with the Barnacle app token (GITHUB_TOKEN events would not trigger workflows). Safety: 10-minute quiet window, 24h lookback, skips drafts, merge conflicts, pending mergeability, and auto-merge PRs (close cancels auto-merge); revalidates state, head, and CI attachment immediately before mutating; per-PR budget of two sweeper closes and a per-sweep cap of ten; reopen-on-unknown ownership so a stranded close (silent) always loses to a spurious reopen (visible); manual dispatch supports dry_run. Accepted tradeoffs are documented inline: shared-SHA PR topologies can mask a dropped run (skip-only miss; run.pull_requests matching would break fork PRs), and app-auth failover at worst doubles the close budget.
|
ClawSweeper status: review started. I am starting a fresh review of this pull request: ci: hourly sweeper re-fires dropped pull_request CI runs This is item 1/1 in the current shard. Shard 0/1. This placeholder means the worker is alive and reading the current context. I will edit this same comment with the actual review when the claws are done clicking. Crustacean status: shell secured, claws on keyboard, evidence pebbles being sorted. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a3c741e0cd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| steps: | ||
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | ||
| with: | ||
| ref: ${{ github.sha }} |
There was a problem hiding this comment.
Restrict privileged dispatches to the trusted branch
When this workflow is manually dispatched from a non-default ref, github.sha resolves to that selected branch or tag, so the later dynamic import executes its scripts/github/pr-ci-sweeper.mjs after the GitHub App token has been created. A collaborator able to push a branch and dispatch workflows can therefore run unreviewed branch code with the Barnacle installation credentials, bypassing protected-main review. Restrict manual execution to the trusted default branch and load the sweeper implementation only from that trusted revision before exposing the App token.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 13db4fc9ab
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const botCloseCount = events.filter( | ||
| (event) => | ||
| event.event === "closed" && | ||
| event.actor?.type === "Bot" && | ||
| sweeperLogins.has(event.actor.login), |
There was a problem hiding this comment.
Attribute close budgets to the sweeper, not Barnacle
This counts every close by a login in sweeperLogins as a sweeper attempt, but .github/workflows/auto-response.yml:32-47 authenticates with the same Barnacle apps and scripts/github/barnacle-auto-response.mjs:865-870 closes PRs. If such a PR is later legitimately reopened and that reopened CI event is dropped, one prior Barnacle close reduces its retry budget and two make this sweeper skip it entirely as refire-budget-exhausted; attribute budget entries to this workflow rather than the shared app identity.
Useful? React with 👍 / 👎.
|
Codex review: needs real behavior proof before merge. Reviewed July 18, 2026, 3:08 PM ET / 19:08 UTC. Summary PR surface: Tests +241, Config +67, Other +273. Total +581 across 4 files. Reproducibility: yes. by source: manually dispatching the workflow from a collaborator-controlled ref makes github.sha select that checkout, and the workflow imports its module after creating the app token. Review metrics: 1 noteworthy metric.
Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Proof guidance:
Risk before merge
Maintainer options:
Next step before merge
Maintainer decision needed
Security Review findings
Review detailsBest possible solution: Keep the scheduled repair loop on trusted default-branch code and move any manual dry-run capability behind a trusted-default-branch dispatcher or remove it until that boundary can be enforced before the app token is available. Do we have a high-confidence way to reproduce the issue? Yes, by source: manually dispatching the workflow from a collaborator-controlled ref makes github.sha select that checkout, and the workflow imports its module after creating the app token. Is this the best way to solve the issue? No. The hourly repair idea is bounded, but the proposed manual-dispatch design does not preserve the repository’s trusted-code boundary; a fixed trusted dispatcher or schedule-only implementation is safer. Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 5fdb0cd24e68. Label changesLabel justifications:
Evidence reviewedPR surface: Tests +241, Config +67, Other +273. Total +581 across 4 files. View PR surface stats
Security concerns:
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
|
Merged via squash.
|
…0889) * ci: add hourly PR CI sweeper for dropped pull_request runs Fresh PRs race GitHub's merge-ref computation: the open-event CI run can drop entirely or be created as an un-rerunnable startup_failure (~10-16 runs daily). The sweeper lists recently updated open PRs hourly, finds heads whose only pull_request-event CI runs are startup failures (or missing), and re-fires the event by close/reopen with the Barnacle app token (GITHUB_TOKEN events would not trigger workflows). Safety: 10-minute quiet window, 24h lookback, skips drafts, merge conflicts, pending mergeability, and auto-merge PRs (close cancels auto-merge); revalidates state, head, and CI attachment immediately before mutating; per-PR budget of two sweeper closes and a per-sweep cap of ten; reopen-on-unknown ownership so a stranded close (silent) always loses to a spurious reopen (visible); manual dispatch supports dry_run. Accepted tradeoffs are documented inline: shared-SHA PR topologies can mask a dropped run (skip-only miss; run.pull_requests matching would break fork PRs), and app-auth failover at worst doubles the close budget. * test(ci): exercise pr-ci-sweeper runner with a faked client * fix(test): lint-clean pr-ci-sweeper runner fakes
What Problem This Solves
Fresh PRs race GitHub's merge-ref computation: the pull_request-open CI run either never attaches to the head SHA or is created as
startup_failure/BuildFailed/(Unknown event)— whichgh run rerunrefuses to retry. Repo history shows ~10–16 such runs daily across all authors; every one of the last four maintainer PRs (#110472, #110528, #110623, #110812) needed a manual close/reopen to get CI. PRs silently sit without CI until someone notices.Why This Change Was Made
Diagnosed in #110812 (which documented the manual remedy in AGENTS.md). The workflow triggers in
ci.ymlare correct; the drop is GitHub-side, so the durable fix is automation of the proven remedy. An hourlypr-ci-sweeperworkflow scans recently updated open PRs, finds heads whose pull_request-event CI runs are missing or allstartup_failure, and re-fires the event by close/reopen using the Barnacle app token (required:GITHUB_TOKEN-authored events do not trigger workflows).Sweeping is destructive automation, so the guards carry the design: a 10-minute quiet window (merge-ref settling), 24h lookback, skipping drafts, merge conflicts, pending mergeability, and auto-merge PRs (closing cancels auto-merge, which reopening does not restore — this protects the generated locale-refresh PRs); authoritative revalidation of state/head/CI attachment immediately before mutation; a per-PR budget of two sweeper closes (counted against sweeper app identities only) and a per-sweep cap of ten; and reopen-on-unknown close ownership so the silent failure (PR stranded closed) always loses to the visible one (spurious reopen). Manual
workflow_dispatchsupportsdry_runfor observability. Decision logic lives inscripts/github/pr-ci-sweeper.mjsper the Barnacle pattern, with the classify function pure and table-tested.Two accepted tradeoffs are documented inline: a SHA shared by two PRs can mask one PR's dropped run behind the other's (a skip-only miss — matching
run.pull_requestsinstead would misclassify fork PRs, where GitHub leaves that array empty), and app-auth failover can at worst double the per-PR close budget to four.User Impact
Contributor/maintainer experience: dropped PR-open CI self-heals within the hour instead of requiring someone to notice and close/reopen manually. No product or runtime surface changes.
Evidence
gh apion run 29634070586 showspath: BuildFailed,display_title: "(Unknown event)", empty referenced workflows; 100 most recentstartup_failureruns span five days, allpull_requestevents.node scripts/run-vitest.mjs test/scripts/pr-ci-sweeper.test.ts— 12 table cases pin the decision surface (refire on missing/startup-failure-only runs; skips for draft, lookback, quiet window, conflict, pending mergeability, auto-merge, attached/queued/failed runs, exhausted budget).node scripts/check-script-declarations.mjs— 235 contracts match (new.d.mtsincluded); oxlint clean; workflow YAML follows the pinned-action + app-token-with-fallback pattern fromauto-response.yml.openclaw-barnacle[bot]) verified against live close events; app-slug is also passed at runtime from the token step outputs.workflow_dispatchwithdry_run: trueto observe classification, then a live dispatch.