mirror of
https://github.com/go-gitea/gitea.git
synced 2026-05-08 23:02:10 +00:00
Backport #37311 by @silverwind ## Problem Workflow-level concurrency groups were evaluated — and jobs were parsed — before the run was persisted, so `run.ID` was `0` and `github.run_id` in the expression context resolved to an empty string. Expressions like: ```yaml concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} cancel-in-progress: true ``` collapsed to `<workflow>-` on every push event (`head_ref` is empty on push), so `cancel-in-progress` cancelled in-progress runs across **unrelated branches**, not just the current one. Reproduced on a 1.26 instance: - push to `master` → `ci` run starts - push to `feature-branch` → the `master` run gets cancelled GitHub Actions' documented semantic: on push events `github.run_id` is unique per run, so the group is unique → no cancellation; on PR events `github.head_ref` is the source branch → cancellation is per-PR. ## Fix Insert the run **before** parsing jobs or evaluating workflow-level concurrency, so `run.ID` is populated in time for every expression that reads `github.run_id` — not just the concurrency group, but also `run-name`, job names, and `runs-on`. `jobparser.Parse` now runs inside the `InsertRun` transaction, after `db.Insert(ctx, run)`. Workflow-level concurrency evaluation runs next and only mutates `run` in memory. All concurrency-derived fields (`raw_concurrency`, `concurrency_group`, `concurrency_cancel`) plus `status` and `title` are persisted in a single final `UpdateRun` at end-of-transaction — one `INSERT` + one `UPDATE` per run in both the concurrency and non-concurrency paths (matches pre-branch parity, one fewer `UpdateRepoRunsNumbers` `COUNT` than the interim state). `GenerateGiteaContext` now sets `run_id` from `run.ID` unconditionally; every caller passes a persisted run. **Verification**: tested end-to-end on a 1.26 deployment. Before the patch, two successive `ci` pushes (one to master, one to a feature branch) cross-cancelled each other. After the patch, the same pushes — in both orders (master→branch, branch→master) — run to completion simultaneously across 15+ runs with zero cancellations. **Regression tests** in `services/actions/context_test.go`: - `TestEvaluateRunConcurrency_RunIDFallback` — unit check that `EvaluateRunConcurrencyFillModel` resolves `github.run_id` from `run.ID`. - `TestPrepareRunAndInsert_ExpressionsSeeRunID` — full-flow check: calls `PrepareRunAndInsert` with `${{ github.run_id }}` in both `run-name` and the concurrency group, then asserts the persisted `Title`, `ConcurrencyGroup`, and `RawConcurrency` contain / survive the run's ID. Re-ordering `db.Insert` relative to either parse or concurrency eval fails this test. ## Relation to #37119 [#37119](https://github.com/go-gitea/gitea/pull/37119) also moves concurrency evaluation into `InsertRun` but keeps it **before** `db.Insert`, then tries to populate `run_id` only when `run.ID > 0` — which is still `0` at that call site, so the cross-branch leak would survive that PR as written. This PR fixes the ordering so that `run.ID` is actually populated at eval time, and broadens it to cover parse-time expression interpolation too. --- This PR was written with the help of Claude Opus 4.7 Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: Claude (Opus 4.7) <noreply@anthropic.com>