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:
I am aware that I can just name the workflows hard coded, e.g. deploy-web-${{ github.ref }}
, but this would go against the "idiot-proofing" I talked about before. Not to mention it would be very annoying having every concurrency group be dynamic except for the ones with workflow_call
, which would always need to be hard coded.
I am also aware I can set the concurrency at the job
level in the release.yaml
workflow, like this:
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!
${{ github.workflow_ref }}-${{ github.ref }}
seems to be the most error-proof combination I've found${{ 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).
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).
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