From a394b54f82df610d29e6a07936753a501d5861bc Mon Sep 17 00:00:00 2001 From: Adam Moussa Date: Tue, 28 Jul 2026 17:22:51 -0400 Subject: [PATCH] fix(ci): key multi-job reusable concurrency on a literal job id ci-python-app.yaml and ci-mobile-ios.yaml built their concurrency group from ${{ github.job }}. In a called workflow that expression evaluates to the caller's job id, not the job's own id, so every job in the reusable resolved to the same group. With cancel-in-progress: true they cancelled each other. Observed in pr-reviewer after repinning it off a ref that predates the concurrency blocks: one run, ci / lint cancelled 1s after start by a sibling, ci / subproject-tests succeeded, and the aggregator failed on the cancelled dependency. Replaces the expression with the job id written out literally in all 7 groups, and records the reason at the first block in each file. --- .github/workflows/ci-mobile-ios.yaml | 16 +++++++++++----- .github/workflows/ci-python-app.yaml | 14 ++++++++++---- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci-mobile-ios.yaml b/.github/workflows/ci-mobile-ios.yaml index db07e6b..f29874f 100644 --- a/.github/workflows/ci-mobile-ios.yaml +++ b/.github/workflows/ci-mobile-ios.yaml @@ -122,10 +122,16 @@ jobs: runs-on: ubuntu-latest timeout-minutes: ${{ inputs.js-timeout-minutes }} # cancel-in-progress is TRUE: superseding a push should abandon the older - # CI run, which produces no external side effects. `github.job` is in the - # key so the three jobs here do not serialise against each other. + # CI run, which produces no external side effects. + # + # The trailing segment is the job id written out literally, NOT + # `${{ github.job }}`. In a called workflow that expression evaluates to the + # CALLER's job id, so every job here would resolve to the same group and, + # with cancel-in-progress on, cancel its own siblings. Observed live in + # pr-reviewer: `lint` was cancelled one second in by a sibling and the + # aggregator failed on the cancelled dependency. concurrency: - group: ci-mobile-ios-${{ github.workflow }}-${{ github.ref }}-${{ inputs.working-directory }}-${{ github.job }} + group: ci-mobile-ios-${{ github.workflow }}-${{ github.ref }}-${{ inputs.working-directory }}-js cancel-in-progress: true defaults: run: @@ -194,7 +200,7 @@ jobs: runs-on: macos-26 timeout-minutes: ${{ inputs.ios-timeout-minutes }} concurrency: - group: ci-mobile-ios-${{ github.workflow }}-${{ github.ref }}-${{ inputs.working-directory }}-${{ github.job }} + group: ci-mobile-ios-${{ github.workflow }}-${{ github.ref }}-${{ inputs.working-directory }}-ios-build cancel-in-progress: true defaults: run: @@ -289,7 +295,7 @@ jobs: if: always() runs-on: ubuntu-latest concurrency: - group: ci-mobile-ios-${{ github.workflow }}-${{ github.ref }}-${{ inputs.working-directory }}-${{ github.job }} + group: ci-mobile-ios-${{ github.workflow }}-${{ github.ref }}-${{ inputs.working-directory }}-ci cancel-in-progress: true steps: - name: Require all jobs to have succeeded diff --git a/.github/workflows/ci-python-app.yaml b/.github/workflows/ci-python-app.yaml index 2151138..cbf58b4 100644 --- a/.github/workflows/ci-python-app.yaml +++ b/.github/workflows/ci-python-app.yaml @@ -50,8 +50,14 @@ jobs: lint: runs-on: ubuntu-latest timeout-minutes: 10 + # The trailing segment of every group in this file is the job id written + # out literally, NOT `${{ github.job }}`. In a called workflow that + # expression evaluates to the CALLER's job id, so all four jobs here would + # resolve to one group and, with cancel-in-progress on, cancel each other. + # Observed live in pr-reviewer: `lint` was cancelled one second in by a + # sibling and the aggregator failed on the cancelled dependency. concurrency: - group: ci-python-app-${{ github.workflow }}-${{ github.ref }}-${{ inputs.source-dirs }}-${{ github.job }} + group: ci-python-app-${{ github.workflow }}-${{ github.ref }}-${{ inputs.source-dirs }}-lint cancel-in-progress: true steps: - uses: actions/checkout@v7 @@ -100,7 +106,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 concurrency: - group: ci-python-app-${{ github.workflow }}-${{ github.ref }}-${{ inputs.source-dirs }}-${{ github.job }} + group: ci-python-app-${{ github.workflow }}-${{ github.ref }}-${{ inputs.source-dirs }}-test-collect cancel-in-progress: true steps: - uses: actions/checkout@v7 @@ -124,7 +130,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 10 concurrency: - group: ci-python-app-${{ github.workflow }}-${{ github.ref }}-${{ inputs.source-dirs }}-${{ github.job }} + group: ci-python-app-${{ github.workflow }}-${{ github.ref }}-${{ inputs.source-dirs }}-subproject-tests cancel-in-progress: true steps: - uses: actions/checkout@v7 @@ -150,7 +156,7 @@ jobs: if: always() runs-on: ubuntu-latest concurrency: - group: ci-python-app-${{ github.workflow }}-${{ github.ref }}-${{ inputs.source-dirs }}-${{ github.job }} + group: ci-python-app-${{ github.workflow }}-${{ github.ref }}-${{ inputs.source-dirs }}-ci cancel-in-progress: true steps: - name: Require all jobs to have succeeded