Repository navigation
Conversation
…pen alarms A PR that GITHUB_TOKEN opens or pushes gets pull_request runs held for approval (#240 sat with CI action_required), while a workflow_dispatch run needs none. ci.yml now takes workflow_dispatch, and hack/bot-ci-dispatch runs it on each same-repo autobump/* branch opened by github-actions[bot] that has no run at its head, re-running a first-attempt failure once. It keeps one bot ci.yml run in flight (BOT_CI_MAX_INFLIGHT) and defers while a human PR's CI is queued, since one run is about 39 jobs against a 20-40 job pool. The three nightly PR jobs call it after create-pull-request, and the new hourly 'Bot sweep' workflow (job ci-fill) fills in what they deferred. hack/autobump-branch-reset generalises the digest job's stale-branch step to all three PR jobs: a bump branch whose workflow files differ from main's is deleted before create-pull-request, whose force-push GitHub would refuse. The merge gate's checks move to hack/gate_lib.sh. ci_status reads ci.yml at a SHA through a pull_request run or a dispatch run on the PR branch; once the dispatched run is green, held entries of 'CI' and 'Auto-merge Dependabot PRs' no longer block. base_ok ignores release-only bot commits on main, so the three release commits per launcher release no longer force a sync. The gate prints every open ci-alarm issue and refuses (exit 4) a PR that changes .github/ or shipped paths while one is not acked; ledger, docs, bench and test PRs are never refused. ci.yml gains a last job, alarms, which warns on every open ci-alarm issue and on a missing, disabled or quiet Watchdog workflow, and never fails. dependabot-auto-merge.yml is deleted: it ran gh pr merge --auto on an unprotected main, merged nothing in 500 runs, and held a run on every bot PR.
…top on an unreadable run pool
…er, and batch the release hack/bot-merge runs as job merge of the hourly bot sweep. Each pass merges at most one open autobump/* PR that github-actions[bot] opened from this repository (never autobump/amend-*, never a ledger-amendment, Dependabot or fork PR, never a PR that touches .github/). A PR merges only when, at its head SHA: hack/pins policy origin/main SHA says auto, or a maintainer (a User with OWNER, MEMBER or COLLABORATOR association) approved that SHA and no maintainer's latest review requests changes; ci_status is green; every canary engine the policy lists passed hard and exact in the newest canary.yml run's canary-result artifact for that SHA (exact false labels canary-drift and needs an approval; isa-skip, too-big and fail never merge); base_ok holds and ledger-rules.json would stay current, else freshness.yml is dispatched to regenerate the branch (three times per group per UTC day at most, never while a freshness run for it is in flight); main's newest completed ci.yml run is green and no ci-main, ci-missing, release: or red:canary alarm is open; and fewer than MAX_BOT_MERGES_PER_DAY (6) bot merges landed today, unless the policy marks the PR urgent. Urgent PRs go first, then the oldest. The merge is a squash pinned with --match-head-commit, and its body ends with a "Bot-merge: pr= group= urgent=" line that later passes count. A GITHUB_TOKEN merge starts no push workflow, so the pass dispatches ci.yml on main after it. Every candidate keeps one comment that states what blocks it, or the evidence it merged on, edited in place. A refused merge prints GitHub's reason and ends the pass. Release batching follows the merge step: auto-release.yml is dispatched with mode=changed when main holds Bot-merge commits no aur-v* tag contains, main's HEAD ci.yml run succeeded, no release workflow is queued or running, no auto-release run succeeded since the newest of those merges, and an urgent fix is among them, or no other bot PR is queued with an allowed verdict and green CI, or the newest tag is 6 h old. hack/bot-ci-dispatch --workflow canary.yml now dispatches with engines=auto and only for heads whose policy lists canary engines; ci-fill runs it after the ci.yml step. The merge job checks out main only, with no persisted credentials, and runs after ci-fill even when ci-fill had an error, since it reads every piece of evidence itself. tests/bot_merge_test.sh runs the script against a fake GitHub (tests/bot_merge_fake_gh.py) and real fixture repositories, one case per rule and per release-batching condition (54 cases); 20 mutants of the script each fail at least one case. ci_wiring_test.sh checks that the merge job needs ci-fill, checks out main only and runs hack/bot-merge, and that the script never checks out a ref.
… held or foreign run The canary evidence came from the newest canary.yml run at the head SHA, whatever it was. Two runs the canary workflow makes broke that. A pull_request run of a bot PR is held for approval (action_required, no artifact); when one is newer than the dispatched run, the PR stayed blocked. And an isa-skip re-dispatch reruns only the skipped engines, so its artifact lacks the engines the first run passed, and a multi-engine group could never merge. A dispatched run on another branch at the same SHA also counted as this PR's evidence. The runs at the head are now its pull_request runs and its workflow_dispatch runs on the PR branch, held ones aside, the same set ci_status and bot-ci-dispatch read. All must be complete and the newest must have succeeded. Each mapped engine is decided by the newest canary-result artifact for the SHA that holds it, so the first run's pass and the re-dispatch's pass combine. A newer run with no readable artifact for the SHA blocks, since what it found is unknown, and an engine entry that is not a result object is not a verdict. The evidence comment links every run whose result counted. Eight new cases cover the held run, the re-dispatch (passing and failing), a newer failure superseding a pass, a newest run red on an unmapped engine, a newest run without an artifact, another branch's run and a null entry (62 cases in all). Nine mutants of the canary reader each fail at least one case.
88plug
force-pushed
the
claude/fresh-bot-merge
branch
from
October 30, 2026 00:43
ad571bc to
f49ad67
Compare
… read only this PR's canary runs The pins policy now reads merge-base(main, head)..head, in bot-merge and in the canary dispatch. Against origin/main it read main's later commits backwards: one ledger commit turned an auto patch into changes-defaults (checked with the freshness branch's hack/pins), and a stale head's group became "mixed", so it was neither merged nor regenerated. base_ok passes over release commits, but those can touch the head's lines: each release rewrites K3D_GPU_VERSION in packaging/install.sh, on the line above the k3d pin. GitHub refused such a merge on every pass, and since the refused PR was the oldest, nothing else merged and no release was batched. A head that git merge-tree cannot merge onto main is now regenerated like a stale one. The regeneration dispatch sends group only: freshness.yml takes reason as known-issue:<id> and fails any other value. The canary reader lists runs through the REST API and keeps only those whose head is this repository's PR branch. canary.yml runs on pull requests into any base, so a fork could open one from the bot's commit and its result counted as this PR's. A breaker that cannot read main's CI runs or the alarm issues still blocks every merge, and now also fails the pass, so the sweep goes red instead of staying green while merging nothing.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Merges auto-eligible bot PRs without a human once every piece of evidence is green at the head, one PR per pass, stops merging whenever main or a release is red, and batches the releases those merges trigger. Part P7 (bot-merge) of the freshness spec.
Stacked on #253 (bot-pr-ci:
hack/gate_lib.sh,hack/bot-ci-dispatch,bot-sweep.yml). Until #253 merges, this diff also shows its two commits; the commit of this part is the top one. Merge #253 first.What changes
hack/bot-merge(new), run hourly as jobmergeofbot-sweep.yml. One pass:autobump/*branches, opened bygithub-actions[bot], labelledautobump. Neverautobump/amend-*,ledger-amendment, Dependabot or fork PRs; never a PR that adds, changes or renames a file under.github/.hack/pins policy MB SHAsaysauto, MB being the merge-base of main and SHA (the PR's own diff, so a ledger or release commit on main is not read backwards as the PR's change), or a maintainer approved that SHA (reviewAPPROVED,commit_id= head,user.typeUser, association OWNER, MEMBER or COLLABORATOR). A maintainer's latest review requesting changes blocks. Labels never change the verdict.ci_status SHA BRANCHis green (the dispatched ci.yml run counts; a heldaction_requiredrun does not).canarylist: thecanary.ymlruns at the SHA are itspull_requestandworkflow_dispatchruns whose head is this repository's PR branch (read through the REST API, which names the head repository: canary.yml runs on pull requests into any base, so a fork's PR from the same commit is not this PR's evidence), held (action_required) ones aside. All must be complete and the newest must have succeeded. Each mapped engine is decided by the newestcanary-resultartifact, made for that SHA, that holds it, and must showverdict: pass,hard: true,exact: true. Per-engine because canary.yml's isa-skip re-dispatch reruns only the skipped engines. A newer run with no readable artifact blocks. A hard pass withexact: falselabels the PRcanary-driftand needs a maintainer approval.isa-skip,too-bigand every other verdict never merge.base_okholds,git merge-treemerges the SHA onto main without a conflict (base_ok passes over release commits, and each release rewritesK3D_GPU_VERSIONinpackaging/install.sh, the line above the k3d pin), andrules_staledoes not hold. Otherwise it dispatchesfreshness.yml -f group=<g>(noreason: freshness.yml takesknown-issue:<id>only) to regenerate the branch: at most 3 per group per UTC day, counted from today's dispatched freshness runs, and never while a freshness run for that group is queued or running.ci.ymlrun on main (push or dispatch) succeeded, and no openci-alarmissue's key starts withci-main,ci-missing,release:orred:canary. A breaker that cannot be read blocks every merge and fails the pass.MAX_BOT_MERGES_PER_DAY(default 6) bot merges on main today (UTC), unless the policy marks the PRurgent.gh pr merge N --squash --match-head-commit SHA --subject ... --body ...; the body ends withBot-merge: pr=N group=G urgent=B, which later passes use to count today's merges and to find unreleased ones.ci.ymlon main, because a GITHUB_TOKEN merge starts no push workflow. A refused merge prints GitHub's reason and ends the pass.<!-- bot-merge -->), edited in place only when its text changes. The comment states what blocks the PR, or the evidence it merged on (policy, CI run, canary run, sweep run).Release batching, in the same pass after the merge step. It dispatches
auto-release.yml -f mode=changed(at most once) when all of these hold:aur-v*tag contains;auto-release,build-and-push,aur-deployorpackagesrun is queued or running;hack/bot-ci-dispatch --workflow canary.ymlnow dispatches with-f engines=auto, and only for heads whose policy (from the merge-base, as above) lists canary engines. It fetches the head from origin to read the policy and never checks it out.bot-sweep.ymlci-fill runs it after the ci.yml step, and its checkout fetches full history for the policy diff.bot-sweep.ymljobmerge:needs: ci-fill;timeout-minutes: 20;mainonly, withpersist-credentials: false.It runs with
if: !cancelled(), so an error in ci-fill does not stop merging: the merge job reads every piece of evidence itself.rules_staleruns the merged tree'smodel_opt.pyin a subshell withoutGH_TOKEN.Interfaces this part consumes from parallel parts
Each was checked against the sibling branch or its working tree.
auto-release.ymltakes the choice inputmodewith the valuechanged;[ci-alarm] <key>: <title>;ci-main,ci-missing,release:<ver>andred:<file>, so a red canary nightly isred:canary.yml.hack/pins policyreturns the engine list ascanary(its in-progresspinlib/policy.pyemits"canary": sorted(...)).bot-mergefails closed and comments when that list is missing.freshness.ymlis not on a pushed branch yet. This part dispatches it withgrouponly; its in-progress version rejects anyreasonbutknown-issue:<id>.freshness.ymlshould set arun-namethat containsgroup=<g>.canary.ymltakes the inputsenginesandattempt, and uploads an artifact namedcanary-resultholdingcanary-result.jsonwith{sha, ref, engines: {<id>: {verdict, hard, exact, ...}}}.shais the PR head forpull_requestruns and the branch head for dispatches.redispatchjob re-dispatches only the isa-skip engines on the same ref (attempt + 1, at most 3). The canary reader handles both.engines=autoselects by the merge-base diff, and the policy'scanarylist comes from P6. An engine the policy maps but auto did not select shows as "canary result lacks engine E" in the PR's comment, and the PR waits for a human.Tests
tests/bot_merge_test.sh(new)tests/bot_ci_dispatch_test.shtests/gate_merge_test.shtests/ci_wiring_test.shtests/claims_check.shtests/ledger_drift_check.pyhack/ci-images.env)tests/bot_merge_test.shruns the script against a fake GitHub (tests/bot_merge_fake_gh.py) and real git fixture repositories.gate_lib.shwritten before ci: CI on bot PRs by dispatch, gate acceptance and alarm surfacing #253 existed. The fixtures hold under both readings ofci_status, which differ on a queued run newer than a completed one.hack/bot-mergeeach fail at least one case. Examples: drop the head pin; count bot approvals; ignore the approval SHA; disable the breaker, the cap or the regeneration cap; skip the.githubcheck; let isa-skip pass; skip the release busy check.hack/pinsjudges the diff it is given, as the real one does: a file the BASE..HEAD diff changes and the PR's own diff does not makes the verdict review and the group mixed. Cases for a ledger-only main move (merged, the policy called with the merge-base), a release commit that conflicts with the head (regenerated, no merge attempt, and the next PR merges in the same pass), a fork's pull_request canary run at the same SHA (not counted) next to this PR's own approved one (counted), and an unreadable breaker (exit 1). Seven more mutants each fail at least one case: policy against origin/main (in bot-merge and in the canary dispatch), no conflict check, a reason sent to freshness.yml, no head-repository filter, no head-branch filter, an unreadable breaker that exits 0.ci.ymlgets jobbot-merge-tests, placed just beforealarms, because #253's wiring test requiresalarmsto stay the last job.ci_wiring_test.shgains three checks:hack/bot-merge;Merge order
Merge #253 first, then this one only after these are on main: #252 (P1:
auto-release.ymlinputmode, without which every release dispatch fails), #265 (P3: pin-guard, which CI must carry before a bot merge relies on it), #258 (P5:canary.yml) and P6 (hack/pins,freshness.yml). Withouthack/pins:Not done here
The end-to-end acceptance happens after merge, with eyes on each step:
aur-vtag is cut.Separately, a review-class PR should stay open with its blocking comment.
No repository settings, App permissions, secrets or branch protection change. If the App-token upgrade lands later, the PR author changes from
github-actions[bot]to the App's bot account. The candidate filter inhack/bot-mergeand inhack/bot-ci-dispatchwould then need that login added.