concurrencycontinuous-integrationgithub-actions

Using workflow filename in concurrency group for workflows started by workflow_call in GitHub Actions


I'm using concurrency in my GitHub Actions to limit all workflows to one simultaneous run per workflow, per ref.

This ensures that only the latest push to a branch or pull request runs per workflow, saving both time and money.

This setup worked perfectly, until I tried using workflow_call. The issue arises because my concurrency group naming, which relies on ${{ github.workflow }}-${{ github.ref }}, doesn't work as expected with workflow_call events.

The problem is that ${{ github.workflow }} in the callee workflow inherits the name of the caller workflow (similar to how permissions work). This leads to a naming conflict, causing the callee to fail immediately. I can't seem to find a way to get the filename inside a workflow called by workflow_call, since according to the docs the callee inherits everything from the caller.

I need a solution that keeps the concurrency group label dynamic. ${{ github.workflow }} was ideal because workflow filenames are unique in the workflows folder, ensuring no accidental duplicates from other team members. I want to maintain this level of "idiot-proofing" while resolving the conflict with workflow_call.

Here is my setup:

deploy.web.yaml

name: "deploy / web"

on:
  workflow_dispatch:
  workflow_call:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  staging:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Web deployment to staging not configured yet"

release.yaml

name: "release"

on:
  push:
    branches: [main]

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  metadata:
    runs-on: ubuntu-latest
    steps:
      - uses: googleapis/release-please-action@v4
  web:
    needs: [metadata]
    uses: ./.github/workflows/deploy.web.yaml

EXPECTED

The release.yaml workflow should and call deploy.web.yaml successfully. If release.yaml is then called again while the previous call is still running it should cancel the oldest one and proceed with the newest.

ACTUALITY

deploy.web.yaml fails immediately on the first call with this error:

Canceling since a deadlock for concurrency group 'release-refs/heads/main' was detected between 'top level workflow' and 'api'

NOTE

Before answering this please be know that:

jobs:
  web:
    concurrency:
      group: ${{ github.workflow }}-web-${{ github.ref }}
      cancel-in-progress: true
    uses: ./.github/workflows/deploy.web.yaml

This however, does not define concurrency at the workflow level and would mean If I were to call the workflow by another event, say workflow_dispatch, it would not have concurrency protection. Not to mention we also be hard coding the name for the group once again.

Hopefully there is a reasonable solution for this within the context of what I am looking for!


Solution

  • TLDR

    Uniqueness

    ${{ github.workflow }} was ideal because workflow filenames are unique in the workflows folder

    As per documentation (emphasis mine):

    The name of the workflow. If the workflow file doesn't specify a name, the value of this property is the full path of the workflow file in the repository.

    So, if anyone names their workflow name: A and another name: A it would be a collision. On the other hand ${{ github.workflow_ref }} has this property, as it's a path, no matter the name.

    Additionally (unlike what might have seemed in my comment), you would need ${{ github.ref }} as it refers to the ref tirggering the worklow (it might be refs/heads/<branch_name> if this is the trigger). ${{ github.workflow_ref }} ref refers to the one which hosts the file (usually main).

    In your case it shouldn't matter as the workflow is set to main, but in some other branch-based workflow it might (so I'd deem it less "automatic" as per your jargon).

    Caller vs callee

    This seems to be exactly what I was looking for. Only thing I still need is for it to not get overwritten by the caller workflow in workflow_call

    Unfortunately it isn't as there is no way to differentiate between the two via available contexts to the concurrency field.

    There seems to be a way to distinguish between caller and the calle as described here but for OIDC claim tokens, for example:

    {
        "workflow_ref": "org/repo/.github/workflows/top-level-workflow.yml@refs/heads/main",
        "job_workflow_ref": "org2/repo2/.github/workflows/reusable-workflow-3.yml@v1",
    }
    

    where workflow_ref is akin to the one mentioned above and job_workflow_ref is the callee reference at the end of the chain (which would suffice in your case).

    Other things tried

    Just to be sure, I've also checked in a separate repo specifying the reusable workflow via "local" (as yours ./.github/workflows/deploy.web.yaml) and "global" references (octo-org/example-repo/.github/workflows/reusable-workflow.yml@main), no difference