ci: stop image publish legs from cancelling each other (#191)

Every main push since the concurrency change cancelled two of the three
image builds: the build job's concurrency group named only the ref, so
the app, updater and computer legs of one run shared it and cancelled
each other, and the publish job never ran. Main has not published an
edge image since. The build and publish groups now name the matrix leg.

The Playwright report publisher also read the jobs of a run's latest
attempt, so a re-run already in progress made it fail on an empty
conclusion. It now reads the attempt that finished.


Claude-Session: https://claude.ai/code/session_01R8L4XgqjAemFR2remB5hVY

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
cynarAI 2026-10-10 06:50:07 +02:00 committed by GitHub
parent 6ace14c3c8
commit 634b3f823b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 13 additions and 6 deletions

View File

@ -44,9 +44,11 @@ jobs:
env: env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RUN_ID: ${{ github.event.workflow_run.id }} RUN_ID: ${{ github.event.workflow_run.id }}
RUN_ATTEMPT: ${{ github.event.workflow_run.run_attempt }}
shell: bash shell: bash
run: | run: |
result="$(gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${RUN_ID}/jobs?per_page=100" \ # Read the attempt that finished: a re-run already in progress has no conclusion yet.
result="$(gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${RUN_ID}/attempts/${RUN_ATTEMPT}/jobs?per_page=100" \
--jq '[.jobs[] | select(.name == "Web E2E / Web E2E") | .conclusion] | unique | if length == 1 then .[0] else error("expected exactly one Playwright job") end')" --jq '[.jobs[] | select(.name == "Web E2E / Web E2E") | .conclusion] | unique | if length == 1 then .[0] else error("expected exactly one Playwright job") end')"
case "$result" in case "$result" in
success|failure) ;; success|failure) ;;

View File

@ -174,7 +174,7 @@ jobs:
runs-on: ${{ matrix.runner }} runs-on: ${{ matrix.runner }}
timeout-minutes: 90 timeout-minutes: 90
concurrency: concurrency:
group: publish-server-image-build-${{ github.ref }} group: publish-server-image-build-${{ github.ref }}-${{ matrix.name }}-${{ matrix.arch }}
cancel-in-progress: ${{ github.ref == 'refs/heads/main' }} cancel-in-progress: ${{ github.ref == 'refs/heads/main' }}
permissions: permissions:
contents: read contents: read
@ -261,7 +261,7 @@ jobs:
runs-on: [self-hosted, evoo-ci, Linux] runs-on: [self-hosted, evoo-ci, Linux]
timeout-minutes: 15 timeout-minutes: 15
concurrency: concurrency:
group: publish-server-image-publish-${{ github.ref == 'refs/heads/main' && 'main' || github.ref }} group: publish-server-image-publish-${{ github.ref == 'refs/heads/main' && 'main' || github.ref }}-${{ matrix.name }}
cancel-in-progress: false cancel-in-progress: false
permissions: permissions:
contents: read contents: read

View File

@ -9,6 +9,7 @@ const workflowText = readFileSync(
); );
interface WorkflowJob { interface WorkflowJob {
if?: string; if?: string;
concurrency?: { group?: string };
needs?: string; needs?: string;
"runs-on"?: string; "runs-on"?: string;
strategy?: { matrix?: { arch?: string[]; include?: Array<Record<string, string>> } }; strategy?: { matrix?: { arch?: string[]; include?: Array<Record<string, string>> } };
@ -106,9 +107,13 @@ describe("server image publish workflow", () => {
expect(workflowText).toContain( expect(workflowText).toContain(
"cancel-in-progress: ${{ github.event_name == 'pull_request' }}", "cancel-in-progress: ${{ github.event_name == 'pull_request' }}",
); );
expect(workflowText).toContain("publish-server-image-build-${{ github.ref }}"); // Matrix legs share a job-level concurrency group unless it names the leg, and a shared
expect(workflowText).toContain( // group cancels sibling legs, so no image is published.
"publish-server-image-publish-${{ github.ref == 'refs/heads/main' && 'main' || github.ref }}", expect(build.concurrency?.group).toBe(
"publish-server-image-build-${{ github.ref }}-${{ matrix.name }}-${{ matrix.arch }}",
);
expect(publish.concurrency?.group).toBe(
"publish-server-image-publish-${{ github.ref == 'refs/heads/main' && 'main' || github.ref }}-${{ matrix.name }}",
); );
expect(workflowText).toContain("push: false"); expect(workflowText).toContain("push: false");
for (const match of workflowText.matchAll(/uses:\s+([^\s#]+)/g)) { for (const match of workflowText.matchAll(/uses:\s+([^\s#]+)/g)) {