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.
Last updated: August 2026

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

  1. 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.
  2. Define before you reference. An alias must appear after its anchor in document order. &env_vars on job1 can be aliased by job2; 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.
  3. One file, one document. There is no cross-file, cross-repository, or include mechanism in YAML. An anchor in ci.yml is invisible to release.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:

  1. Locate every &name and 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 on env: covers the sibling steps: as well.
  2. Substitute each *name with 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.
  3. 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 fix job1, and job5 two 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_job still appear as two distinct jobs with their own logs and their own billing.
  • A quoted glob is not an alias. In branches: ['*'] or paths: ['!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.

RequirementCorrect mechanismWhy
Repeat an identical env: map, services: block, or path-filter list within one workflow fileYAML anchor + aliasZero runtime cost; purely textual
Bundle several steps that run inside the caller's job, with inputs and outputsComposite actionExecutes on the caller's runner; parameterizable
Share a multi-job pipeline across repositories, with inputs, secrets, and its own runnersReusable workflowJob-level, versioned by @ref, centrally maintained
Give teams a starting point they will then own and editWorkflow templateCopied once; deliberately disconnected
The same block but with one value differentComposite action or reusable workflowAnchors 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.

Loading diagram...
YAML Anchor Expansion Order: Parser First, Actions Engine Second
Test Your Knowledge

A platform engineer writes the following in a single GitHub Actions workflow file:

defaults: &base
  runs-on: ubuntu-latest
  timeout-minutes: 30

jobs:
  build:
    <<: *base
    timeout-minutes: 60
    steps:
      - run: make build
What happens when this file is pushed?

A
B
C
D
Test Your Knowledge

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?

A
B
C
D
Test Your Knowledge

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?

A
B
C
D