Push run on main is cancelled when a second merge lands seconds later #26
Labels
No labels
Agent
Ignore
Agent
In Progress
Agent
Planned
Agent
Planning
Agent
Ready
Agent
Triage
Compat
Breaking
Kind
Bug
Kind
Documentation
Kind
Enhancement
Kind
Feature
Kind
Security
Kind
Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
lucas/nanoKontroller2#26
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
Problem
The
Build / build (push)check on merge commit530bd777ae041662a64195eb7254beca641f5ff7(merge of #23) is red with "Has been cancelled" — run #19, API run 16494. The job did not fail: it was cancelled 17s in, midapt-get install, with no failing step and the runner reportingcontext canceled:Two pull requests were merged into
mainwithin seconds of each other (#23 →530bd77, then #22 →2669dcd): run #19 (530bd77) was cancelled at13:07:27.2, run #20 (2669dcd) reached the same runner at13:07:28.6and finished green. The firstmainrun was superseded by the second. Nothing in the tree caused it —.forgejo/workflows/build.ymlinstalls its dependencies with explicitapt-getand never readsmise.toml, the only file #23 touched.The cancellation is Forgejo's default concurrency management, not a repository setting: with no
concurrencykey, a newpush/pull_request (synchronize)event cancels any other invocation of the same workflow (Forgejo Actions reference,concurrency). The consequence is that an intermediatemaincommit keeps a permanently red check for content that was never broken, while the proof that the content is fine sits one commit later.Desired behaviour: a
pushrun onmainis never cancelled — everymaincommit keeps a verdict that is not retracted. Pull-request runs keep superseding each other, since an obsolete PR commit's result is not interesting and the runner's time is better spent on the newest one.Plan
Context
Add a workflow-level
concurrencysetting to.forgejo/workflows/build.ymlthat disables Forgejo's automatic cancellation forpushevents while keeping it forpull_requestevents. This is the repository's only workflow and its only job. No other behaviour of the workflow changes; nothing else in the repository is touched.Approach
Single change, in
.forgejo/workflows/build.yml: insert a top-levelconcurrencyblock between theon:block (currently lines 3-6) andjobs:(currently line 8), so the file head reads exactly:Why this exact form:
groupkey, deliberately. Per the Forgejo reference, without a groupcancel-in-progress: falsemeans no concurrency management at all — runs proceed in parallel up to the runner's capacity and all of them finish. Adding agroupwould instead serialise every run of the workflow into a strict queue, which is not wanted: the runs are independent of one another.forgejo.event_name, not on the ref. The Forgejo docs showcancel-in-progress: ${{ forgejo.ref_name != 'main' }}as their example, but the documented sampleforgejocontext for a pull-request event carries"ref": "refs/heads/main"/"ref_name": "main", so a ref-based test risks evaluatingfalseon PR runs and switching off exactly the cancellation that should stay on.event_nameis unambiguous.pushruns of this workflow can only come frommain(on.push.branches: ["main"]), so "event is notpull_request" is precisely "push onmain".concurrencyis documented as accepting expressions over theforgejo,inputsandvarscontexts, andevent_nameis a documented field of theforgejocontext.No other file changes. Nothing in the repository reads or duplicates this setting (
.forgejo/workflows/contains onlybuild.yml).Critical files & anchors
.forgejo/workflows/build.yml— insertion point is the blank line betweenpull_request:(line 6) andjobs:(line 8). Thejobs:/build:block below is untouched.Verification
Open the change as a pull request. The pull request's own
Buildrun must appear and complete green. That is the check: Forgejo evaluates theconcurrencyexpression server-side when it creates the run, so a malformed expression or an unsupported key surfaces as a workflow that fails to parse or a run that never starts, rather than as a build failure.Assumptions & contingencies
concurrencykey with expressions (reported as Forgejo 17.0.1; unverified — confirm first, no session tool reads the instance version). If the PR produces a workflow parse error or the expression is rejected, fall back to the plain documented formconcurrency:/cancel-in-progress: false(no group, no expression) and say so on the pull request: that keepsmainruns from being cancelled — the point of this issue — at the cost of also leaving superseded PR runs to finish.runner.capacity, default 1) that this change cannot influence (unverified — confirm first, the runner's configuration is not reachable from this repository). With capacity 1, twomainruns execute one after the other instead of simultaneously; both still run to completion, which is what this change is for.For the current workflow, I want queuing. All runs should finish. And since they are independent of each other, they can run concurrently.
That is supported, and it is a two-line change to
.forgejo/workflows/build.yml.The cancellation was not configured anywhere — it is Forgejo's default concurrency management: with no
concurrencykey, a newpush/pull_request (synchronize)event cancels any other invocation of the same workflow on the same branch (Forgejo Actions reference,concurrency). Turning that off is exactly what you describe:So without a group,
cancel-in-progress: falsegives "all runs finish, concurrently". Note the distinction: adding agrouppluscancel-in-progress: falsewould give strict queuing (serialized, one at a time) — which is not what you want here, since the runs are independent.This instance is Forgejo 17.0.1, which has the feature.
Option A — never cancel, anywhere (push and PR)
Every run of
Buildfinishes, whatever lands afterwards. Downside: force-pushing a PR eight times in a row leaves eight builds running against obsolete commits.Option B — never cancel on
main, keep superseding on PR branchesmaincommits each get a verdict that is never retracted; PR branches keep the "only the newest commit matters" behaviour, so runner time is not spent on commits nobody will look at. This is the documented expression form (contextsforgejo/github,inputs,varsare allowed inconcurrency).My recommendation is B: the point of this issue is that an
maincommit kept a red check for content that was never broken, and PR runs that are superseded by a newer push are a different case where cancelling is the useful default. But A is defensible if you want "every run that was ever started has a result" as a flat rule.Caveat on "concurrently"
concurrency: cancel-in-progress: falseremoves the cancellation; it does not add runner capacity. From the logs of runs #19/#20 both merges were dispatched to the same runner, so whether twomainruns actually overlap depends on that runner's configured capacity (runner.capacityin itsconfig.yml, default 1). With capacity 1 they will simply run one after the other — still both to completion, which is the property you asked for. If genuine parallelism matters, the runner's capacity needs raising too, and that is an instance-side change, not a repository one.About
530bd77Independent of which option you take: the existing red check on
530bd777ae041662a64195eb7254beca641f5ff7stays red until run #19 is re-run from the Actions UI. The change above only affects future runs.I cannot push branches or edit files in this session — this is a proposal. Say which option you want and I can open it as a PR via the normal implementation flow.