GitHub Actions YAML Anchors/Aliases Not Working in Workflows
A workflow using YAML anchors and aliases (&name, *name, the << merge key) to deduplicate steps does not behave as expected, because GitHub Actions does not support anchor/alias reuse the way plain YAML does.
What this error means
A workflow relying on YAML anchors/aliases to share step blocks is rejected or ignores the reuse, because the Actions parser does not expand anchors for workflow reuse.
# anchors like this are NOT reliably supported for reuse in Actions
defaults: &common
run:
shell: bash
jobs:
a:
<<: *common # not the supported reuse mechanismDiagnose it: print the context before you change anything
Most workflow-expression bugs are not syntax errors, they are an expression reading something that is empty. GitHub resolves a missing property to an empty string instead of failing the run, so a wrong reference looks like a logic bug rather than a mistake. Dump the contexts first and you will usually see the answer immediately.
- name: Dump contexts
run: |
echo '--- github ---' ; echo '${{ toJSON(github) }}'
echo '--- needs ---' ; echo '${{ toJSON(needs) }}'
echo '--- steps ---' ; echo '${{ toJSON(steps) }}'
echo '--- matrix ---' ; echo '${{ toJSON(matrix) }}'
echo '--- inputs ---' ; echo '${{ toJSON(inputs) }}'Check the context is allowed where you used it
Contexts are not available everywhere. The same expression can be valid in a step if and invalid in a job if, which is why an expression that works in one workflow fails when moved.
| Where you wrote it | Contexts available there |
|---|---|
run-name | github, inputs, vars |
concurrency | github, inputs, vars |
Top-level env | github, secrets, inputs, vars |
jobs.<id>.if | github, needs, vars, inputs |
jobs.<id>.steps.if | github, needs, strategy, matrix, job, runner, env, vars, steps, inputs |
jobs.<id>.outputs | Full access, including secrets |
Reusable workflow outputs | github, jobs, vars, inputs |
Common causes
Actions does not use anchors for reuse
GitHub Actions provides its own reuse primitives and does not support YAML anchors/aliases/merge keys as a way to deduplicate jobs or steps.
Expecting plain-YAML behavior
Anchors work in generic YAML tooling, so it is natural to assume Actions honors them - but the workflow schema is the contract, not raw YAML merge semantics.
How to fix it
Use the supported reuse primitives
Factor shared steps into a composite action or a reusable workflow, and use a matrix to fan out variants.
# reusable workflow for shared jobs
jobs:
ci:
uses: ./.github/workflows/shared-ci.yml
# or a composite action for shared steps
- uses: ./.github/actions/setupPick the right tool for the duplication
- Composite action: reuse a sequence of steps inside jobs.
- Reusable workflow: reuse whole jobs across workflows.
- Matrix: run the same job over multiple parameter combinations.
Catch it before it reaches CI
Every failure in this cluster is statically detectable. actionlint parses workflow expressions, checks context availability against the same rules above, and validates needs references, so these bugs never need to cost you a run.
# one-off
docker run --rm -v "$(pwd):/repo" --workdir /repo rhysd/actionlint:latest -color
# as a job, before anything expensive runs
- uses: actions/checkout@v4
- run: |
bash <(curl -s https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash)
./actionlint -colorHow to prevent it
- Do not rely on YAML anchors/aliases for workflow reuse.
- Use composite actions, reusable workflows, and matrices instead.
- Treat the Actions schema as the source of truth, not raw YAML semantics.