CI: skip main's repeat of a passed PR tree; cancel superseded runs (#107) #108
No reviewers
Labels
No milestone
No project
No assignees
3 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
schaefera/utStudyBIble!108
Loading…
Reference in a new issue
No description provided.
Delete branch "ci/main-runs"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #107 (options 1 and 2; option 3, a second runner, stays open until the cost is worth it).
What
changesjob decides (tools/ci/main-skip.sh), and every other job is skipped withrust. It still runs in full if:concurrencywithcancel-in-progress: a newer push cancels the older run of the same PR (keyed on the PR number) or of main.ci-preview/**branches run CI the way main does, for trying a change like this one; docs/09 says how.Blast radius
Tier: trunk
Trunk paths touched:
.forgejo/workflows/ci.yml,tools/ci/main-skip.sh,tools/ci/main-skip-test.shTesting tier: maximum (CI) (also: ship on sight, for docs/09)
Gate / rollback: revert this merge. Every rule defaults to running in full: an unknown, pending or failed status, or any error in the script's checks, gives
run=true.Proof
sh tools/ci/main-skip-test.shpasses all 13 cases. CI'schangesjob also runs it on every PR run.8829d29), the case "first parent's status unknown" fails, which shows the review 297 fix is needed.3f95111, repeated the full pipeline on a tree its PR had already passed. Checked by hand against the new rule: equal trees, no cache input changed, PR headd97afa1green. So it would have been skipped.ci-preview/107: the branch runs CI the way main does; draft PR #109 was opened only so the runner's scheduler sees the branch, then closed.697fbd2) is a docs-only child of the head. Its run 182 was cancelled by M's push (concurrency).4e0d501) has parents X and the head, with the head's tree. In run 183,changesloggedmain-skip: the first parent 697fbd2… has status 'failure'and setrun=true. The full pipeline then ran, and its rerun is green. The first attempt'sappfailure was mem-check noise (PSS 165922 kB against a 165000 kB budget; #96, comment 2236).bb41f19) has parents M and the head, with the head's tree. In run 186,changesloggedmain-skip: same tree as PR head 4e8317a…, which passedand setrun=false; rust, audit, app, e2e and click show skipped.98d88fa(all six jobs, on the rerun of run 188). The first attempt failed inappon the same mem-check noise (165092 kB; #96, comment 2248).Review
Reviewer: fresh-context
code-review:4e8317a:Nothing deferred or left open.
Review-Confidence: 100/100. A full high-level review, a delta review of the fixes, and live preview runs of both the full path and the skip path leave no unreviewed code. Any remaining risk falls to the fallback, which runs in full.
Confidence by part
main-skip.sh): high. 13 local cases, plus the live preview of both a full run and a skip.github.event.pull_request.numberbeing set on Forgejo pull_request runs.ci-previewbranch needs a draft PR (docs/09). Main's skipped run takes about 10 s on the runner.🤖 Generated with Claude Code
Level: high (trunk: .forgejo/workflows/, tools/ci/). Commit
8829d29b0b. Range origin/main...HEAD.Files reviewed: .forgejo/workflows/ci.yml, .forgejo/workflows/presets.yml, docs/09-quality-gates.md, tools/ci/main-skip.sh, tools/ci/main-skip-test.sh, CLAUDE.md.
sh tools/ci/main-skip-test.shpasses 10/10 locally.Findings: one medium and one low, both inline (ci.yml:19, main-skip.sh:51). Nothing outside the diff.
Not a defect, noted for the preview run: the API-status parse and the fetch-depth 2 parent availability are not covered by the local test (status is mocked), so the ci-preview/107 run is the only proof of those.
@ -12,0 +16,4 @@# Keyed on the PR number, as a pull_request run's ref is not unique per PR.concurrency:group: ci-${{ github.event.pull_request.number || github.ref }}cancel-in-progress: trueMEDIUM. cancel-in-progress on main lets a skipped run kill a full one and the cache refresh is lost for good. Scenario: PR1 changes Cargo.lock and merges; main starts a full run A (the only run that writes the new sccache/registry keys, per docs/09). PR2, up to date with PR1 and green, merges while A runs. PR2's merge M passes main-skip.sh because it diffs only against HEAD^1 (= A's commit), which shows no cache-input change, so M is skipped. M's concurrency group (ci-refs/heads/main) cancels A. The new Cargo.lock keys are never written by main, and no later merge sees a cache-key change, so every PR cold-builds until the next lockfile change. Same for a direct push followed by a merge. Fix: do not cancel main's runs (cancel-in-progress only for pull_request, e.g.
${{ github.event_name == 'pull_request' }}), or have main-skip.sh compare against the last commit whose full run completed, not HEAD^1. Note the stated expected result (M cancels F) is exactly this behaviour.@ -0,0 +48,4 @@if [ -z "$state" ]; thenstate=$(curl -fsS -H "Authorization: token ${GITHUB_TOKEN:-}" \"$GITHUB_SERVER_URL/api/v1/repos/$GITHUB_REPOSITORY/commits/$head/status" |grep -o '"state":"[a-z]*"' | head -n 1 | cut -d'"' -f4) || trueLOW. The pattern
"state":"[a-z]*"assumes compact JSON. If the Forgejo API returns indented output ("state": "success"), grep matches nothing, state is unknown and run=true every time: safe, but the optimisation silently never fires, and the local test cannot see it because it mocks MAIN_SKIP_STATUS. Allow optional whitespace:grep -o '"state": *"[a-z]*"'and cut on the last quoted field.Review 297: both findings are fixed in
4e8317a.medium · ci.yml:19 (a skip cancels main's full run, and the cache refresh is lost). Agreed.
cancel-in-progresson main, as option 2 asks, and closes the hole in the skip rule instead.main-skip.shnow also requires the first parent's own run (HEAD^1's combined status) to besuccess.8829d29), "first parent unknown" gives run=false; on the new one, run=true.low · main-skip.sh:51 (the parse assumes compact JSON). Fixed: the pattern is now
'"state" *: *"[a-z]*"'. It is checked on indented sample JSON and on the live API (both givesuccess).The preview plan changes to match. On
ci-preview/107:Tests:
sh tools/ci/main-skip-test.shpasses 13 of 13 cases.Re-review, level: medium. Commit
4e8317aab5. Range 8829d29..HEAD.Files reviewed: tools/ci/main-skip.sh, tools/ci/main-skip-test.sh, .forgejo/workflows/ci.yml (changes job and concurrency block).
No defects found. The first-parent rule fails toward running in every case I traced. A cancelled or failed first-parent run reports failure. A running one reports pending. A commit with no statuses reports pending, and an unreachable API gives an empty state. An all-skipped run that does not read as plain success also falls back to a full run. The status field is taken from the first "state" in the combined-status JSON, which is the top-level one, and the whitespace-tolerant regex is safe. I did not run the preview branch or the local test script.
WIP: CI: skip main's repeat of a passed PR tree; cancel superseded runs (#107)to CI: skip main's repeat of a passed PR tree; cancel superseded runs (#107)Merge gate (medium) on
98d88fa: approved.Checked:
4e8317ais98d88fa, a docs/09 wording change about the draft PR for ci-preview branches. It touches no code.98d88fais success, with all six jobs green: changes, rust, audit, app, e2e, click.changeslog (job 1850) shows "main-skip: same tree as PR head4e8317a..., which passed" and run=false.