chrismdp / land-a-pull-requestA pull request lands once CI has runPublic · Version 3 of 3#3 · chrismdp · Yesterday 2:27pm · 0.8ShowingLatest#2 · chrismdp · 27 Oct 2026, 11:57am · 0.6#1 · chrismdp · 27 Oct 2026, 11:49am · 0.6ForkWho may see each partWhere each part runsYAMLChecksNo mistakesGitHub Actions, land.ymlGitHub: pull requests, checks, and main's ruleland.yml: read the checks and the commit and apply the rule, with a token that only readsland.yml: ship, show or ask, running nothing from the changeThe reviewer's browserCI's run and its committhe pull request, its head andthe issue's label events[open, at CI's commit, everyrequired check passed][ship][show][merged][ask]the review requestthe pull request and CI's resultTriggerCI's pull_request run finisheson a build/issue-* branch ofthis repositoryGitHub starts land.yml on workflow_run when CI finishes on a factory branch. It runs main's code, never checks out the branch, and reads the commit with git as data. Its reading job has no concurrency group, since a stranger's pull request from the same branch starts a run too, and GitHub keeps only one run waiting in a group, so a newer one would displace the real one's; each run only reads GitHub, afresh, and leaves quietly when there's nothing to do. Its landing job's group is keyed on the pull request and never cancelled, so nothing stops it between the merge and its comment (#153) Decided in #160's follow-up: only build/issue-* branches, and only pull_request runs, which run once per commit. People merge their own pull requests2Linked to chrismdp/issue-to-pull-request#tokenConnectionThe repository, through itsown Actions tokenLinked to chrismdp/issue-to-pull-request#appConnectionThe repository, through itsown GitHub AppLinked to chrismdp/ci-a-commit#checksData · untrustedThe head commit's checks:test (1.22), test (stable), Bugreview, Security review andClassifyData · untrustedThe pull request's headcommit and the branch'scommits since main, from gitThe checks and the commits carry text a stranger's patch could steer. Only the rule's outcome and its own fixed reasons pass on, so Classify's reasons are linked, never quotedDataThe build rule, from main:what moves a verdict towardsaskWhether it may merge is derived, not stored: the head commit's checks and the build rule run on that commit, read when CI finishes. No record line, digest or artifact from the build is read (#160 rules 2 and 3)Code · sortSort by GitHub's record: is thepull request open, its head stillthe commit CI ran on, and hasevery required check on itfinished and passed, with bothreviews done? Find who lastadded the issue's build label,and whether it's still onDecided in #160's follow-up: a required check that failed or hasn't finished, or a head that moved, means it exits quietly. A failed check is retry-a-build's or the fixer's, and each posts its own new comment when it gives upCode · sortSort by rule, passing on onlythe commit's sha, theoutcome, the labeller's loginand the rule's own reasonsfrom fixed text: Classify'sverdict, ship, show or ask,moved towards ask by therule's tests, a commit that isn'tthe factory's, a fix commit orNits left unfixedThe rule can only move a verdict towards ask, never away: the rule's own tests on paths, size, binaries and symlinks, a commit since main that isn't the factory's, or Classify saying ask means ask; a fix commit on the branch, or Nits left unfixed, means at least show (#160) Decided by the maintainer: ship and show merge with no person, once both reviews have finished and Classify says ship or show. This rule step is what cleans the reviews' and Classify's text before the merge If the build label was taken off the issue since, by GitHub's record of its labels, a person has stepped in: the rule never ships or shows, and asks, saying the build label was taken off the issue, so a person decides. Who is asked is whoever last added the label, if they can write, otherwise the owner The rule judges the head commit itself, read with git into a worktree at its merge-base with main, not a patch from an artifact. It asks when Classify's check is missing. Only the sha, the outcome, the login and the reasons pass to the land job, as job outputs (#153)4Code · doMerge now, pinned with--match-head-commit to thecommit judged, saying in itsmessage that it shippedThe merge names the commit it judged, so GitHub refuses it if anyone pushed since. No auto-merge anywhere in the factory, so nothing set for an older commit can carry over to a new one (#153, #160) The app's token is minted in the land job only, scoped to this repository (#153)2Code · doMerge now, pinned with--match-head-commit to thecommit judged, saying in itsmessage that it's shownThe app's token is minted in the land job only, scoped to this repository (#153)Outside actionMerge a change the rulepasses into mainAllowAllow, because a merge is undone with git revert, everything in it is already public on its branch, and the maintainer would not be surprised by a change the rule ships or shows. One the rule asks about is where they would be, so that merge is AskCode · doPost a new comment for theperson who added the label,saying what to look at, fromthe rule's reasons and a link toClassify's checkA new comment, since a person must act, with any text escaped: < escaped, mentions broken, length capped (#160 rule 5)Outside actionComment on the merged pullrequestAllowCode · doRequest a review from theperson who added the label,with the rule's reasons and alink to Classify's check, anddon't mergeOutside actionRequest a review, in a newcommentAllowPerson · doReview the pull request,whose CI has passed, andmerge itConnectionThe repository, through thereviewer's own accountOutside actionMerge a change that wasasked about into mainAskMain's rule holds every merge, the app's and the maintainer's, until CI passes. Only the maintainer may skip it, to push a release; the app may notFlow only+−Fit