3.5 YAML Anchors, Aliases & Merge Keys
Key Takeaways
- GitHub Actions has supported YAML anchors (`&`) and aliases (`*`) since September 2025, enabled automatically for every user and repository.
- Merge keys (`<<:`) are NOT supported, because GitHub implemented the YAML 1.2 specification and merge keys are a YAML 1.1 extension - so there is no way to alias a node and override one of its fields.
- Anchors are expanded by the YAML parser before GitHub Actions evaluates anything, so they cannot be conditional, cannot take arguments, and must be defined earlier in the file than the alias that references them.
- Anchors and aliases work only within a single workflow file; cross-file or cross-repository reuse requires a composite action or a reusable workflow.
- When troubleshooting, expand every alias mentally before reasoning about behavior - the UI and logs reflect the expanded document, and one edited anchor silently changes every job that aliases it.
YAML Anchors, Aliases & Merge Keys
The January 2026 blueprint added YAML anchors in two places: authoring them under Author and manage workflows, and expanding and interpreting them under Consume and troubleshoot workflows. That double listing is a strong signal - you will be asked both to write them and to read a workflow that already uses them.
This is also one of the newest topics on the exam. GitHub shipped YAML anchor support in September 2025, enabled automatically for every user and repository. Guides written before that date state flatly that GitHub Actions does not support anchors; guides written carelessly after it claim full YAML merge-key support. Both are wrong, and the gap between them is exactly what a well-written exam item probes.
1. The Syntax: & Defines, * Reuses
An anchor, marked with &, labels a node in the YAML document. An alias, marked with *, inserts that node somewhere else.
jobs:
job1:
env: &env_vars # & defines the anchor on first use
NODE_ENV: production
DATABASE_URL: ${{ secrets.DATABASE_URL }}
steps:
- run: echo "Using production settings"
job2:
env: *env_vars # * reuses the anchored mapping verbatim
steps:
- run: echo "Same environment variables here"
That file is exactly equivalent to writing the mapping out twice:
jobs:
job1:
env:
NODE_ENV: production
DATABASE_URL: ${{ secrets.DATABASE_URL }}
steps:
- run: echo "Using production settings"
job2:
env:
NODE_ENV: production
DATABASE_URL: ${{ secrets.DATABASE_URL }}
steps:
- run: echo "Same environment variables here"
Anchors are not limited to small maps. You can anchor an entire job and reuse the whole configuration:
jobs:
test: &base_job # anchor the complete job definition
runs-on: ubuntu-latest
timeout-minutes: 30
env:
NODE_VERSION: '18'
steps:
- uses: actions/checkout@v6
- name: Set up Node.js
uses: actions/setup-node@v7
with:
node-version: ${{ env.NODE_VERSION }}
- run: npm test
alt-test: *base_job # reuse the entire job configuration
Three rules that follow from how YAML works
- Expansion happens at parse time, before Actions sees the file. Anchors are a feature of the YAML parser, not of GitHub Actions. By the time triggers, contexts, and
${{ }}expressions are evaluated, the document has already been expanded. Anchors therefore cannot be conditional, cannot depend on a context value, and cannot be "called" with different arguments. - Define before you reference. An alias must appear after its anchor in document order.
&env_varsonjob1can be aliased byjob2; the reverse fails to parse. This is why GitHub's own example comments the anchor "define the anchor on first use" - you attach the anchor to the first real occurrence rather than to a separate declaration block. - One file, one document. There is no cross-file, cross-repository, or
includemechanism in YAML. An anchor inci.ymlis invisible torelease.yml. Any exam option proposing cross-file anchor reuse is wrong - that is what reusable workflows and composite actions are for.
2. The Trap: Merge Keys (<<:) Are Not Supported
The blueprint text mentions merge (<<), so candidates reasonably assume GitHub implemented it. It did not.
# This is standard YAML in GitLab CI and Bitbucket Pipelines.
# In GitHub Actions it FAILS with a syntax error.
defaults: &base
runs-on: ubuntu-latest
timeout-minutes: 30
jobs:
build:
<<: *base # <-- NOT SUPPORTED by GitHub Actions
timeout-minutes: 60 # (intended as a partial override)
GitHub's stated reasoning is that it implemented what appears in the YAML 1.2 specification, and merge keys are not part of YAML 1.2 - they are a separate, older YAML 1.1 extension. The practical consequence is the single most important thing to remember about this topic:
[!WARNING] There is no partial override. Without merge keys you cannot take a base mapping and change one field. You either alias the entire anchored node exactly as it is, or you do not use the anchor at all. If you need "the same thing but with a longer timeout", your options are: define separate anchors for each combination, accept the duplication, or move the logic into a composite action or reusable workflow that accepts an input.
+-----------------------------------------------------------------------------+
| WHAT GITHUB ACTIONS DOES AND DOES NOT SUPPORT |
| |
| &anchor Define a reusable node .................... SUPPORTED |
| *alias Insert that node verbatim ................. SUPPORTED |
| <<: *a Merge keys / partial override ............. NOT SUPPORTED |
| cross-file anchor reuse ............................. IMPOSSIBLE IN YAML |
+-----------------------------------------------------------------------------+
3. Reading a Workflow That Uses Anchors (Troubleshooting Domain)
Because expansion happens before execution, the run you are diagnosing is the expanded document, not the compact source you wrote. The Actions UI, the job list, and the logs all reflect the expanded form. When you are handed a workflow and asked to predict its behavior, expand it mentally first.
A three-step reading procedure:
- Locate every
&nameand note precisely which node it labels - a scalar, a mapping, a sequence, or a whole job. The indentation tells you the scope; a common misreading is assuming an anchor placed onenv:covers the siblingsteps:as well. - Substitute each
*namewith a literal copy of that node. Write it out if the question is complex; aliases nest, and a job-level alias can pull in an env map that itself was anchored earlier. - Only then evaluate Actions semantics - triggers,
needs,if:conditions, matrix expansion, and${{ }}expressions - against the expanded text.
[!TIP] The classic diagnostic scenario: an engineer edits an anchored
env:block to fixjob1, andjob5two hundred lines away breaks, because it aliased the same anchor. The symptom looks like an unrelated regression; the cause is that anchors create a single source with many consumers and no version boundary. When you review a change to an anchored node, search the file for every alias that references it.
Two smaller reading traps worth internalizing:
- Anchors do not shorten log output or job names. Two jobs sharing
*base_jobstill appear as two distinct jobs with their own logs and their own billing. - A quoted glob is not an alias. In
branches: ['*']orpaths: ['!docs/**'], the*and!are inside quotes and are plain string characters. Unquoted, YAML would try to read them as an alias or a tag and the file would fail to parse. Quoting is why the glob patterns you have already studied are safe.
4. Choosing Between Anchors, Composite Actions & Reusable Workflows
Anchors are the smallest and least powerful of the three reuse mechanisms. Choosing correctly is a recurring exam pattern.
| Requirement | Correct mechanism | Why |
|---|---|---|
Repeat an identical env: map, services: block, or path-filter list within one workflow file | YAML anchor + alias | Zero runtime cost; purely textual |
| Bundle several steps that run inside the caller's job, with inputs and outputs | Composite action | Executes on the caller's runner; parameterizable |
| Share a multi-job pipeline across repositories, with inputs, secrets, and its own runners | Reusable workflow | Job-level, versioned by @ref, centrally maintained |
| Give teams a starting point they will then own and edit | Workflow template | Copied once; deliberately disconnected |
| The same block but with one value different | Composite action or reusable workflow | Anchors cannot do partial overrides |
[!NOTE] Readability is a real cost. Anchors add a layer of indirection that every future reader must resolve by hand. On a 100-line workflow with two repeated lines, anchors make the file harder to read, not easier. Reach for them when duplication is genuinely painful and strictly local - large repeated
services:definitions, long shared path-filter lists, or several near-identical jobs in one big file.
A platform engineer writes the following in a single GitHub Actions workflow file:
What happens when this file is pushed?defaults: &base
runs-on: ubuntu-latest
timeout-minutes: 30
jobs:
build:
<<: *base
timeout-minutes: 60
steps:
- run: make build
A team maintains ci.yml, which defines &test_env on a shared env: mapping, and release.yml, which needs the same environment variables. An engineer adds env: *test_env to release.yml. What is the result?
An engineer is asked to explain why editing four lines in a 900-line workflow file changed the behavior of a job that was not touched. The file uses YAML anchors heavily. What is the most likely explanation, and what should the reviewer do?