ci: run push workflows only on develop, main and release tags #309

Open
opened 2026-10-19 10:43:44 +02:00 by nessar · 0 comments
Owner

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, Gitea forum thread on autocancellation, adamj.eu on avoiding double runs.
## 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/).
nessar added the ready-for-agent label 2026-10-19 10:43:44 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: nessar/vac-optimizer#309