Every commit pushed to a branch that has an open pull request runs the whole CI pipeline twice. One run comes from the push event and one from the pull_request event. Each pipeline covers validation, tests, the benchmark, build, security scans and image publication.
Of the 61 commits in the last 127 runs, 45 had both a push and a pull-request run. The two runs sit in different concurrency groups, so neither cancels the other. The cost lands on a single runner (srvhome) with a visible queue. The repository's own Gitea Actions guide also warns that overlapping push and pull-request runs of one commit can disturb the benchmark's relative gate.
The "cancel when a newer commit arrives" wish is already met. Within one event and ref, a newer run cancels the older one, and cancelled runs show up in the history.
Solution
Push-triggered CI runs only for integration refs: develop, main, and v* release tags. Every other branch (feature, renovate, release, hotfix) gets CI and its branch-<slug> and sha-<SHA> images only through its pull-request run. Each commit on a PR branch then runs the pipeline once. Cancellation of superseded runs stays as it is.
The trade-off is that a branch with no PR gets no CI and no image. Opening a draft PR early gives a branch its image on demand.
User Stories
As a maintainer, I want one CI run per commit on a branch with an open PR, so that the runner queue and the benchmark gate aren't loaded by a second identical pipeline.
As a developer, I want a newer commit on my PR to cancel its older in-progress or queued run, so that I don't wait behind superseded work. This is already true and must keep working.
As a developer, I want my branch's branch-<slug> image once its PR exists, so that I can run any branch on demand.
As a maintainer, I want merges to develop and main to run CI once and publish branch-develop/branch-main and the sha- image, so that integration images and release promotion keep working.
As a maintainer, I want pushing a vX.Y.Z tag to still trigger CI and promote the existing SHA image, so that the release flow is unchanged.
As a maintainer, I want the PR title, rebase and GitFlow checks and the required status context to be unchanged, so that branch protection still gates merges.
As a reader of the docs, I want the documented triggers to match the workflow, so that nobody expects CI on a PR-less branch push.
Accepted edge cases:
A push to a branch with no PR runs nothing.
A tag that doesn't match v* triggers nothing.
A fork PR behaves as today.
Implementation Decisions
Agreed:
Add a branch and tag filter to the CI workflow's push trigger: branches develop and main, tags v*. Tags must be listed explicitly, because a branch filter alone stops tag pushes from triggering.
Keep the pull_request trigger (opened, reopened, synchronize, edited) and manual dispatch unchanged.
Keep the concurrency group (event name plus ref) and cancel-in-progress, which stays off for tags. No change is needed for the "cancel on new commit" wish.
No gate job and no Gitea API lookup. That approach (the GitLab $CI_OPEN_MERGE_REQUESTS rule, which the original .gitlab-ci.yml had) was considered and declined for simplicity.
Leave the images, required, policy, benchmark and branch-protection logic untouched. They already branch on event type and cover push on develop/main, tag promotion, and same-repo PRs.
Proposed (not yet confirmed as the exact approach):
Work on a new feature/* branch cut from the current origin/develop, with a Conventional Commit such as ci: run push workflows only on develop, main and release tags, and a PR into develop.
Update the prose that describes triggers: the "every branch and tag push" statement and the push/PR-overlap and benchmark notes in the Gitea Actions guide, and the README's trigger summary and image-tag table row "successful push to any branch".
Testing Decisions
A good check here observes external behaviour: which runs the Gitea Actions API lists after real pushes and PR events. It does not re-read the YAML.
The existing policy and image-publisher regression tests cover event handling and don't assert on the trigger list. They should still pass unchanged.
Lint-level check: the project's Prettier format check covers the workflow file, so it should pass locally.
Acceptance is verified live on the PR for this change:
Pushing the feature branch before a PR exists creates no run.
Opening the PR creates a pull_request run and no push run.
A follow-up push creates only pull_request run(s) for the new commit, and the superseded one is cancelled.
After merge, develop shows exactly one push run and the branch-develop image is published.
The first real v* tag shows that tag pushes still trigger and promote. This can only be confirmed at the next release.
Prior art: the run-history analysis from the discussion (count push and PR runs per commit SHA from the Actions API) is the reusable method.
Out of Scope
A gate job or API lookup that skips a push when a PR exists.
Push triggers on release/** or hotfix/**. Their PR runs cover them.
Dropping or narrowing the edited PR trigger, and the several PR events Gitea fires per push (17 of 61 commits had more than one PR run). The existing cancellation collapses them.
The PR run queued on closed PR #307 at close time.
Cross-event cancellation, a main exemption from cancel-in-progress, benchmark-flakiness work, and the migration gate and activation tracked in #268.
Further Notes
Evidence from 127 Gitea runs: 45 of 61 commits had both a push and a PR run. No push run was ever cancelled; every cancelled run was a PR run.
Gitea is 28.1.0. Per-event concurrency works there, and a Gitea forum report says it was fixed in 1.26.
Cancel-in-progress on main could cancel a prior main push run and its sha- image if two merges land close together. Nobody tags a superseded commit, so this was accepted.
Accepted trade-off: the GitLab-era pipeline ran branch CI on PR-less branches. This design drops that, so a PR-less branch gets neither CI nor an image.
## Problem Statement
Every commit pushed to a branch that has an open pull request runs the whole CI pipeline twice. One run comes from the `push` event and one from the `pull_request` event. Each pipeline covers validation, tests, the benchmark, build, security scans and image publication.
Of the 61 commits in the last 127 runs, 45 had both a push and a pull-request run. The two runs sit in different concurrency groups, so neither cancels the other. The cost lands on a single runner (`srvhome`) with a visible queue. The repository's own Gitea Actions guide also warns that overlapping push and pull-request runs of one commit can disturb the benchmark's relative gate.
The "cancel when a newer commit arrives" wish is already met. Within one event and ref, a newer run cancels the older one, and cancelled runs show up in the history.
## Solution
Push-triggered CI runs only for integration refs: `develop`, `main`, and `v*` release tags. Every other branch (feature, renovate, release, hotfix) gets CI and its `branch-<slug>` and `sha-<SHA>` images only through its pull-request run. Each commit on a PR branch then runs the pipeline once. Cancellation of superseded runs stays as it is.
The trade-off is that a branch with no PR gets no CI and no image. Opening a draft PR early gives a branch its image on demand.
## User Stories
1. As a maintainer, I want one CI run per commit on a branch with an open PR, so that the runner queue and the benchmark gate aren't loaded by a second identical pipeline.
2. As a developer, I want a newer commit on my PR to cancel its older in-progress or queued run, so that I don't wait behind superseded work. This is already true and must keep working.
3. As a developer, I want my branch's `branch-<slug>` image once its PR exists, so that I can run any branch on demand.
4. As a maintainer, I want merges to `develop` and `main` to run CI once and publish `branch-develop`/`branch-main` and the `sha-` image, so that integration images and release promotion keep working.
5. As a maintainer, I want pushing a `vX.Y.Z` tag to still trigger CI and promote the existing SHA image, so that the release flow is unchanged.
6. As a maintainer, I want the PR title, rebase and GitFlow checks and the `required` status context to be unchanged, so that branch protection still gates merges.
7. As a reader of the docs, I want the documented triggers to match the workflow, so that nobody expects CI on a PR-less branch push.
Accepted edge cases:
- A push to a branch with no PR runs nothing.
- A tag that doesn't match `v*` triggers nothing.
- A fork PR behaves as today.
## Implementation Decisions
**Agreed:**
- Add a branch and tag filter to the CI workflow's `push` trigger: branches `develop` and `main`, tags `v*`. Tags must be listed explicitly, because a branch filter alone stops tag pushes from triggering.
- Keep the `pull_request` trigger (opened, reopened, synchronize, edited) and manual dispatch unchanged.
- Keep the concurrency group (event name plus ref) and cancel-in-progress, which stays off for tags. No change is needed for the "cancel on new commit" wish.
- No gate job and no Gitea API lookup. That approach (the GitLab `$CI_OPEN_MERGE_REQUESTS` rule, which the original `.gitlab-ci.yml` had) was considered and declined for simplicity.
- Leave the `images`, `required`, `policy`, benchmark and branch-protection logic untouched. They already branch on event type and cover push on `develop`/`main`, tag promotion, and same-repo PRs.
**Proposed (not yet confirmed as the exact approach):**
- Work on a new `feature/*` branch cut from the current `origin/develop`, with a Conventional Commit such as `ci: run push workflows only on develop, main and release tags`, and a PR into `develop`.
- Update the prose that describes triggers: the "every branch and tag push" statement and the push/PR-overlap and benchmark notes in the Gitea Actions guide, and the README's trigger summary and image-tag table row "successful push to any branch".
## Testing Decisions
- A good check here observes external behaviour: which runs the Gitea Actions API lists after real pushes and PR events. It does not re-read the YAML.
- The existing policy and image-publisher regression tests cover event handling and don't assert on the trigger list. They should still pass unchanged.
- Lint-level check: the project's Prettier format check covers the workflow file, so it should pass locally.
- Acceptance is verified live on the PR for this change:
1. Pushing the feature branch before a PR exists creates no run.
2. Opening the PR creates a `pull_request` run and no `push` run.
3. A follow-up push creates only `pull_request` run(s) for the new commit, and the superseded one is cancelled.
4. After merge, `develop` shows exactly one `push` run and the `branch-develop` image is published.
5. The first real `v*` tag shows that tag pushes still trigger and promote. This can only be confirmed at the next release.
- Prior art: the run-history analysis from the discussion (count push and PR runs per commit SHA from the Actions API) is the reusable method.
## Out of Scope
- A gate job or API lookup that skips a push when a PR exists.
- Push triggers on `release/**` or `hotfix/**`. Their PR runs cover them.
- Dropping or narrowing the `edited` PR trigger, and the several PR events Gitea fires per push (17 of 61 commits had more than one PR run). The existing cancellation collapses them.
- The PR run queued on closed PR #307 at close time.
- Cross-event cancellation, a `main` exemption from cancel-in-progress, benchmark-flakiness work, and the migration gate and activation tracked in #268.
## Further Notes
- Evidence from 127 Gitea runs: 45 of 61 commits had both a push and a PR run. No push run was ever cancelled; every cancelled run was a PR run.
- Gitea is 28.1.0. Per-event concurrency works there, and a Gitea forum report says it was fixed in 1.26.
- Cancel-in-progress on `main` could cancel a prior `main` push run and its `sha-` image if two merges land close together. Nobody tags a superseded commit, so this was accepted.
- Accepted trade-off: the GitLab-era pipeline ran branch CI on PR-less branches. This design drops that, so a PR-less branch gets neither CI nor an image.
- Sources: [GitLab workflow docs](https://docs.gitlab.com/ci/yaml/workflow/), [Gitea forum thread on autocancellation](https://forum.gitea.com/t/solved-autocancellation-of-actions-on-prs-not-working/11766), [adamj.eu on avoiding double runs](https://adamj.eu/tech/2025/05/14/github-actions-avoid-simple-on/).
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Problem Statement
Every commit pushed to a branch that has an open pull request runs the whole CI pipeline twice. One run comes from the
pushevent and one from thepull_requestevent. Each pipeline covers validation, tests, the benchmark, build, security scans and image publication.Of the 61 commits in the last 127 runs, 45 had both a push and a pull-request run. The two runs sit in different concurrency groups, so neither cancels the other. The cost lands on a single runner (
srvhome) with a visible queue. The repository's own Gitea Actions guide also warns that overlapping push and pull-request runs of one commit can disturb the benchmark's relative gate.The "cancel when a newer commit arrives" wish is already met. Within one event and ref, a newer run cancels the older one, and cancelled runs show up in the history.
Solution
Push-triggered CI runs only for integration refs:
develop,main, andv*release tags. Every other branch (feature, renovate, release, hotfix) gets CI and itsbranch-<slug>andsha-<SHA>images only through its pull-request run. Each commit on a PR branch then runs the pipeline once. Cancellation of superseded runs stays as it is.The trade-off is that a branch with no PR gets no CI and no image. Opening a draft PR early gives a branch its image on demand.
User Stories
branch-<slug>image once its PR exists, so that I can run any branch on demand.developandmainto run CI once and publishbranch-develop/branch-mainand thesha-image, so that integration images and release promotion keep working.vX.Y.Ztag to still trigger CI and promote the existing SHA image, so that the release flow is unchanged.requiredstatus context to be unchanged, so that branch protection still gates merges.Accepted edge cases:
v*triggers nothing.Implementation Decisions
Agreed:
pushtrigger: branchesdevelopandmain, tagsv*. Tags must be listed explicitly, because a branch filter alone stops tag pushes from triggering.pull_requesttrigger (opened, reopened, synchronize, edited) and manual dispatch unchanged.$CI_OPEN_MERGE_REQUESTSrule, which the original.gitlab-ci.ymlhad) was considered and declined for simplicity.images,required,policy, benchmark and branch-protection logic untouched. They already branch on event type and cover push ondevelop/main, tag promotion, and same-repo PRs.Proposed (not yet confirmed as the exact approach):
feature/*branch cut from the currentorigin/develop, with a Conventional Commit such asci: run push workflows only on develop, main and release tags, and a PR intodevelop.Testing Decisions
pull_requestrun and nopushrun.pull_requestrun(s) for the new commit, and the superseded one is cancelled.developshows exactly onepushrun and thebranch-developimage is published.v*tag shows that tag pushes still trigger and promote. This can only be confirmed at the next release.Out of Scope
release/**orhotfix/**. Their PR runs cover them.editedPR trigger, and the several PR events Gitea fires per push (17 of 61 commits had more than one PR run). The existing cancellation collapses them.mainexemption from cancel-in-progress, benchmark-flakiness work, and the migration gate and activation tracked in #268.Further Notes
maincould cancel a priormainpush run and itssha-image if two merges land close together. Nobody tags a superseded commit, so this was accepted.