CI: stop main-branch runs from holding up PR runs #107

Closed
opened 2026-10-28 13:30:26 +00:00 by claude-bot · 2 comments
Collaborator

The runner runs one job at a time (runner_capacity = 1) and Forgejo Actions has no run priority, so every merge to main queues the whole pipeline (rust → audit → app → e2e → click, more than 10 min) ahead of PR pushes that come after it.

Owner decision (2026-10-28): do 1 and 2 now. Watch the queue before option 3 (a second runner or a PR-only runner label, in ~/dev/infrastructure), because that one costs real money.

1. Don't repeat a PR's CI on main

A PR is brought up to date with origin/main before merge (merge-base rule), so the merge commit's tree equals the tree of the PR head that already passed. On push: main, a first cheap job compares HEAD^{tree} with HEAD^2^{tree}. If they match (and HEAD is a merge), the heavy jobs are skipped. Otherwise (direct push, stale merge) the full pipeline runs. Alternative if that gets fiddly: drop push: main and add a nightly schedule run on main.

To check first:

  • Cache scoping. Do PR runs restore caches that main runs saved (sccache key includes ci.yml's hash, plus cargo registry, app-build and sword-modules)? If Forgejo scopes PR caches like GitHub does (PRs read the default branch's caches, writes stay per-PR), skipping main runs would leave the caches cold. In that case, keep a cache-warming step on main, or let the nightly run warm them.
  • click artifact. Is the arm64 click from main runs used anywhere? If so, keep that job on main.

2. concurrency on main pushes

concurrency: { group: ci-${{ github.ref }}, cancel-in-progress: ${{ github.ref == 'refs/heads/main' }} } so a newer main push cancels an older queued or running main run. Check that Forgejo 17 honours concurrency (test with two quick pushes on a branch) before relying on it.

Trunk (.forgejo/workflows/): full review. Start after #85's PR merges (one feature at a time on the single runner).

The runner runs one job at a time (`runner_capacity = 1`) and Forgejo Actions has no run priority, so every merge to main queues the whole pipeline (rust → audit → app → e2e → click, more than 10 min) ahead of PR pushes that come after it. Owner decision (2026-10-28): do 1 and 2 now. Watch the queue before option 3 (a second runner or a PR-only runner label, in `~/dev/infrastructure`), because that one costs real money. ## 1. Don't repeat a PR's CI on main A PR is brought up to date with `origin/main` before merge (merge-base rule), so the merge commit's tree equals the tree of the PR head that already passed. On `push: main`, a first cheap job compares `HEAD^{tree}` with `HEAD^2^{tree}`. If they match (and HEAD is a merge), the heavy jobs are skipped. Otherwise (direct push, stale merge) the full pipeline runs. Alternative if that gets fiddly: drop `push: main` and add a nightly `schedule` run on main. To check first: - **Cache scoping.** Do PR runs restore caches that main runs saved (sccache key includes `ci.yml`'s hash, plus cargo registry, app-build and sword-modules)? If Forgejo scopes PR caches like GitHub does (PRs read the default branch's caches, writes stay per-PR), skipping main runs would leave the caches cold. In that case, keep a cache-warming step on main, or let the nightly run warm them. - **`click` artifact.** Is the arm64 click from main runs used anywhere? If so, keep that job on main. ## 2. `concurrency` on main pushes `concurrency: { group: ci-${{ github.ref }}, cancel-in-progress: ${{ github.ref == 'refs/heads/main' }} }` so a newer main push cancels an older queued or running main run. Check that Forgejo 17 honours `concurrency` (test with two quick pushes on a branch) before relying on it. Trunk (`.forgejo/workflows/`): full review. Start after #85's PR merges (one feature at a time on the single runner).
Author
Collaborator

Data point for option 2 (2026-10-28): a new push to PR #106 cancelled the run still in progress for its previous head (run 177, every job "Has been cancelled", with no concurrency key in ci.yml). Forgejo already cancels a superseded pull_request run by itself. Still to find out: whether it does the same for push: main runs, which is the case #107 needs.

Data point for option 2 (2026-10-28): a new push to PR #106 cancelled the run still in progress for its previous head (run 177, every job "Has been cancelled", with no `concurrency` key in ci.yml). Forgejo already cancels a superseded `pull_request` run by itself. Still to find out: whether it does the same for `push: main` runs, which is the case #107 needs.
Author
Collaborator

Options 1 and 2 are merged in #108 (e7194a4).

Option 3, a second runner, isn't done. You chose to watch first, because it has a real cost. If PR runs still wait behind main, reopen this issue for it.

Two things came up along the way:

  • The runner's scheduler (infrastructure repo) wakes the runner only for pending runs on main and on open PR heads. A ci-preview/* branch therefore needs an open draft PR (docs/09).
  • Mem-check noise (#96) failed 2 of the last 3 app runs on trees that had already passed. That now costs more queue time than main's repeat runs did.
Options 1 and 2 are merged in #108 (e7194a4). Option 3, a second runner, isn't done. You chose to watch first, because it has a real cost. If PR runs still wait behind main, reopen this issue for it. Two things came up along the way: - The runner's scheduler (infrastructure repo) wakes the runner only for pending runs on main and on open PR heads. A `ci-preview/*` branch therefore needs an open draft PR (docs/09). - Mem-check noise (#96) failed 2 of the last 3 `app` runs on trees that had already passed. That now costs more queue time than main's repeat runs did.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
schaefera/utStudyBIble#107
No description provided.