GitHub Actions finally supports YAML anchors (but not really)

After 4 years of waiting, GitHub finally added YAML anchors to GitHub Actions. Then I read the docs: no merge keys. They shipped half the feature, gained all the complexity costs, and missed the entire point. Here's what actually works and why I'm disappointed.
Frenck in a GitHub hoodie thinking with the title ‘GitHub Actions’. On-screen text shows ‘Anchors &’ and ‘Aliases *’ with green check marks, and ‘Merge keys <<:’ with a red X.

After four years of community requests and 1,650+ reactions on issue #1182, GitHub finally added YAML anchor support on September 18, 2025. Now, about three weeks later, I have mixed feelings.

I'll be honest: when I saw the announcement, I was excited. Finally, after years of begging (I think I've raised a couple of dozen times over the years in any fitting occasion I could), GitHub was going to let us write maintainable workflows. Then I read the details and felt genuinely disappointed. The implementation has a massive limitation that makes the entire feature feel half-baked: merge keys aren't supported. 🙁

Let me be clear about what this means. GitHub announced "YAML anchors" with language about "better conformance with the YAML spec". But they deliberately excluded the one YAML feature that makes anchors actually useful for composing configuration. After four years, they shipped something that technically adds anchors while missing the entire point of why we wanted them.

This is surprising. GitHub had years to get this right. Other CI/CD platforms (GitLab, Bitbucket, CircleCI) implemented full YAML anchor support including merge keys. It's not a technical mystery. The YAML 1.2 specification exists. Yet GitHub chose to ship a partial implementation that forces you into the exact same architectural constraints you already had.

I'm writing this because the announcement felt misleading, and nobody's clearly explaining what you actually get versus what the community asked for. Let me show you both the wins and the significant limitations.

What GitHub actually shipped

The changelog announcement describes "better conformance with the YAML specification." In practice, you get basic YAML 1.2.2 anchors (marked with &) and aliases (marked with *). Define something once, reference it multiple times. Standard stuff.

Here's the critical limitation buried in the documentation: merge keys (<<:) don't work. At all. They're not mentioned anywhere in GitHub's docs. They just silently fail with syntax errors if you try to use them. 🙈

If you've used GitLab CI/CD or Bitbucket Pipelines, this is shocking. Merge keys are what make anchors uniquely useful. They let you create base configurations and extend them with overrides. That pattern is standard in those platforms. GitHub deliberately chose not to implement it.

Without merge keys, you can't do something like this this:

# This does NOT work in GitHub Actions
job1: &defaults
  runs-on: ubuntu-latest
  timeout-minutes: 30
  retries: 3

job2:
  <<: *defaults  # ❌ Syntax error
  timeout-minutes: 60  # Would override timeout if merge keys worked

You're stuck with exact duplication. Want to vary one field? You duplicate the entire block or find another solution. This isn't "better conformance with the YAML specification." This is selective compliance that avoids the hard parts.

GitHub's parser is clearly custom-built rather than using standard YAML libraries that include merge key support. They made an architectural decision to exclude a feature that the community specifically wanted and that other platforms consider essential.

What you can actually do

Despite my disappointment, let me be fair: there are real use cases where anchors eliminate duplication. Just... fewer than you'd expect.

Environment variables across jobs

The most straightforward win. Define your production environment once:

jobs:
  deploy:
    environment: production
    env: &prod_env
      NODE_ENV: production
      API_URL: https://api.example.com
      LOG_LEVEL: warn
      DATABASE_URL: ${{ secrets.DATABASE_URL }}
    steps:
      - run: ./deploy.sh
  
  smoke-test:
    environment: production
    env: *prod_env
    steps:
      - run: ./smoke-test.sh

Simple. That said... this is a situation where merge keys would be needed pretty quickly; but for this example I guess it works.

Path filters

One of the earliest documented production uses comes from developer Aleksa Cukovic:

on:
  push:
    paths: &paths
      - "website/**.[tj]sx?"
      - "website/**.css"
      - "website/**.astro"
      - "website/public/**"
      - "website/package.json"
      - "website/pnpm-lock.yaml"
      - ".github/workflows/deploy.yml"
    branches:
      - main
  pull_request:
    paths: *paths
    branches:
      - main

Maintaining identical path filters across push and pull_request triggers was annoying. Add a new file pattern once, both triggers update automatically.

Service containers

Database configurations get complex fast:

jobs:
  integration-test:
    runs-on: ubuntu-latest
    services:
      db: &postgres
        image: postgres:15
        env:
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: test_db
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432
    steps:
      - run: npm run test:integration
  
  e2e-test:
    runs-on: ubuntu-latest
    services:
      db: *postgres
    steps:
      - run: npm run test:e2e

Multiple test jobs need identical Postgres setup. The service definition is complex enough (health checks, ports, environment) that duplication invites errors.

Home Assistant actually migrated

Despite my reservations, we did refactor Home Assistant's CI workflows. Pull request #152586 reduced our workflow configuration from 504 lines to 206 lines. That's a meaningful improvement.

The PR description acknowledges the limitation directly: "Github finally added support for YAML anchors in workflow files. They don't support key-merge / overrides but it's at least a start".

The migration worked because we had straightforward duplication. But we hit the merge key limitation immediately. We have base test configurations with environment-specific overrides. Without merge keys, we had to choose: accept some duplication or restructure jobs entirely. We chose to accept duplication for those cases. The alternative was making the workflow harder to understand just to work around GitHub's limitation.

Here's the thing: that 298-line reduction (504 to 206) sounds impressive. But with full anchor support including merge keys, we could have cut even more. There are patterns we couldn't consolidate because GitHub's implementation doesn't let us compose configurations.

The security controversy nobody's addressing

Security peeps are not happy about anchors. The problem is analysis tools.

Static analysis tools like actionlint, zizmor, and poutine map workflow configuration to source locations. When something goes wrong, they tell you "line 47 has a permission issue." Anchors break this. One anchor definition at line 10 might be referenced at lines 50, 150, and 300. When a security issue appears, which line is the problem?

One developer put it bluntly: this "blows a massive hole in the larger open source ecosystem's ability to analyze GitHub Actions for correctness and security."

Highway exit ramp meme: a car labeled ‘github actions’ skids toward the exit labeled ‘absolute dogshit YAML features’ instead of continuing toward ‘security’.
William Woodruff's understanding of the GitHub Actions product roadmap.

The concern is valid. Anchors create non-locality. Configuration defined in one place executes in many places. Code review gets harder. Junior developers might reference anchors without understanding what they're inheriting. A permissive permissions block in an anchored definition affects every reference.

Here's my perspective on this: the security concerns are real, but they're solvable. GitLab and Bitbucket have had anchors for years. Security tools adapted. The analysis complexity is a trade-off, not a showstopper.

What frustrates me is that GitHub shipped anchors without merge keys, gaining all the security analysis complexity while missing the compositional benefits that would justify that complexity. We get the costs without the full benefits.

Use anchors for simple, obvious duplication. Avoid them for security-critical configuration like permissions blocks or secret handling. The explicitness of duplication aids security reviews. This is pragmatic advice for today, but it's advice we shouldn't need if GitHub had implemented the feature properly.

Why this feels disappointing

I maintain Home Assistant. We have complex CI/CD needs. I've used GitLab CI/CD before. I know what full YAML anchor support looks like. I know what you can accomplish with merge keys.

When Issue #1182 stayed open for four years with 1,650+ reactions, I assumed GitHub was working on a complete implementation. Maybe they were solving technical challenges. Maybe they were considering the right abstractions.

Then they shipped this. Basic anchors with aliases. No merge keys. No explanation of why. No roadmap for adding them later. Just "better conformance with the YAML specification" that's actually selective conformance that avoids the useful parts.

It's surprising that after all these years, GitHub still can't (or won't) comply with what the YAML specification offers and what competing platforms have implemented. The technical capability exists. Other platforms proved it's doable. GitHub chose not to do it.

The announcement felt misleading. "YAML anchors" implies you're getting YAML anchor functionality. What you actually get is YAML anchor syntax for a subset of use cases. That's a significant difference that the announcement glossed over.

Anchors versus alternatives

You already have other options for eliminating duplication. How do anchors compare?

Reusable workflows let you share entire workflows across repositories. Perfect for organization-wide deployment patterns. Overkill for eliminating duplicate environment variables in one workflow. You also can't add steps before or after a reusable workflow call.

Composite actions bundle step sequences into reusable actions stored in .github/actions/. These work cross-repository and provide proper encapsulation with inputs and outputs. However, they require separate action.yml files, can't specify runners, and add invocation overhead. For simple within-file duplication, composite actions feel heavyweight.

YAML anchors eliminate duplication within one file with zero setup overhead. No separate files, no input definitions, no shell specifications. They work for any YAML node: environment variables, steps, entire jobs.

The architecture forces a choice:

  • Reusable workflows: Multiple jobs, specify runners, but cannot add steps before/after
  • Composite actions: Bundle steps, work cross-repo, allow steps before/after, but cannot specify runners
  • YAML anchors: Within-file only, no parameterization, no composition (no merge keys), but zero setup overhead

Most mature workflows combine all three. Use workflow-level env blocks for truly global variables, anchors for within-file duplication, composite actions for reusable step patterns across projects, and reusable workflows for complete deployment patterns.

The problem is that without merge keys, anchors can't fill the compositional gap. You're still choosing between duplication and heavyweight abstractions for any case where you need base configuration with overrides.

Should you refactor your workflows?

Depends on your pain points and expectations.

Yes, if: You have obvious duplication (environment variables repeated across 3+ jobs, identical step sequences, repeated service containers) and you're okay with the "exact duplication only" constraint. The feature is stable despite being new. GitHub enabled it automatically for all repositories. It won't break existing workflows.

No, if: You expected merge key support and need compositional patterns. You'll be disappointed, like I was. Also skip it if your workflows are small and duplication isn't painful. Adding anchors to a 100-line workflow with minimal repetition makes it harder to read, not easier.

Summing it all up

GitHub Actions now supports YAML anchors for basic duplication elimination. You can anchor environment variables, step sequences, service containers, and path filters. This eliminates maintenance burden for simple repeated configuration.

You cannot use merge keys to compose configurations with overrides. This is a significant limitation compared to other CI/CD platforms that implemented full YAML anchor support years ago. It constrains what you can accomplish and forces architectural decisions about when duplication is acceptable versus when you need composite actions or reusable workflows.

At the time of writing, this feature is just a few weeks old. Real world examples are scarce. Valid concerns have been raised about analysis complexity. Early adopters like us at Home Assistant are still figuring out best practices.

Here's my honest take: use anchors for obvious, simple duplication. Skip them for complex compositional needs or security-critical configuration. The benefits are real but modest. The limitations are frustrating.

And honestly? After four years of waiting, I expected better. I expected GitHub to implement YAML anchors properly, with merge keys, like every other major CI/CD platform. Instead, they shipped something that technically adds anchors while deliberately excluding the functionality that makes them transformative.

The announcement said "better conformance with the YAML specification." That phrase bothers me. YAML anchors exist in the spec alongside merge keys. GitHub implemented half of it and called it conformance. That's not conformance. That's selective implementation.

Maybe GitHub will add merge keys in a future update. Maybe there are technical reasons they haven't shared. Maybe they fundamentally disagree with the community about what's useful. I don't know.

What I do know is that after four years and 1,650+ reactions asking for this feature, the community deserved either full implementation or honest communication about why that's not happening.

../Frenck

Subscribe to Frenck's newsletter and stay updated.

Don't miss anything. Get all the latest posts delivered straight to your inbox. It's free!
Great! Check your inbox and click the link to confirm your subscription.
Error! Please enter a valid email address!