GitHub Actions parses workflow YAML with anchors and aliases since [18 September 2025](https://github.blog/changelog/2025-09-18-actions-yaml-anchors-and-non-public-workflow-templates/). GitHub rejects the merge key (`<<`), so an alias overrides nothing: a copy that differs by one key is out of an anchor's reach.
Scanned 9/10/2026
Install to Claude Code
npx -y skills add staticaland/skills --skill yaml-anchors --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Yaml Anchors?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/staticaland-yaml-anchors)More formats (shields.io, HTML) on the badges page.
---
name: yaml-anchors
description:
Replace repeated configuration in a GitHub Actions workflow with YAML anchors
and aliases. Use when the user wants a shorter workflow, mentions YAML anchors,
or writes the same env, services, permissions, or path filter in more than one
job of the same file.
version: 0.1.0
---
# YAML Anchors
GitHub Actions parses workflow YAML with anchors and aliases since
[18 September 2025](https://github.blog/changelog/2025-09-18-actions-yaml-anchors-and-non-public-workflow-templates/).
GitHub rejects the merge key (`<<`), so an alias overrides nothing: a copy that
differs by one key is out of an anchor's reach.
## Procedure
### 1. Inventory the repeated blocks
Read every workflow in `.github/workflows/`. Find the configuration written more
than once in the same file and record each copy's file, job, and key. These keys
hold most of the duplication.
- `env` maps shared by jobs
- `services` containers, such as a database or a cache
- `permissions`
- `paths`, `paths-ignore`, and `branches` filters under `on.push` and
`on.pull_request`
- `runs-on`, when it names a set of self-hosted labels
Compare the copies of each block key by key and mark them identical or
different. Done when every repeated block has a site list and that verdict.
### 2. Identify anchor candidates
Anchor a block of three lines or more that is identical across two or more sites
in the same file.
Done when each candidate has a verdict: anchor or leave.
### 3. Anchor the identical blocks
Copy each workflow before you change it, so step 4 compares against the file as
it stood. `git show HEAD:<file>` returns the committed file, which is a
different document whenever the working tree already carries an edit.
```bash
cp .github/workflows/ci.yml /tmp/before.yml
```
- **Define on the first copy.** Put `&name` on the first copy in document order
and `*name` on each later copy.
- **Anchor configuration that is already doing a job.** A workflow accepts eight
top-level keys - `name`, `run-name`, `on`, `permissions`, `env`, `defaults`,
`concurrency`, and `jobs` - so a workflow has nowhere to park a block of
definitions.
- **Name the anchor after its content**, such as `&env_vars` or `&code_paths`.
- **Alias whole nodes.** An alias replaces a node. It appends to no list and
overrides no key.
- **Keep the anchored expressions context-free.** An alias copies `${{ ... }}`
as text, and the expression evaluates against the job that holds the alias, so
`${{ matrix.node }}` inside an anchor expands to nothing in a job that has no
matrix.
Done when each anchor has at least one alias, and each alias resolves to an
anchor written earlier in the file.
### 4. Verify
Run `actionlint` on each changed workflow.
```bash
actionlint .github/workflows/ci.yml
```
It reads anchors and aliases, and it reports a merge key by name:
`GitHub Actions does not support YAML merge key "<<"`. A YAML library reports
nothing there, because PyYAML follows YAML 1.1 and expands `<<` in silence.
Then prove the rewrite didn't change behavior.
```bash
python3 -c '
import sys, yaml
before, after = (yaml.safe_load(open(p)) for p in sys.argv[1:])
sys.exit(0 if before == after else "workflows differ")
' /tmp/before.yml .github/workflows/ci.yml
```
Where `import yaml` raises `ModuleNotFoundError`, run the same script through
`uv run --with pyyaml python -c` instead.
`zizmor` 1.29 audits every alias site and prints each finding at the anchor's
line. A finding repeated on one line describes a different job each time.
Report the blocks that an anchor cannot reach and the reason for each.
## Example
```yaml
on:
push:
paths: &code_paths
- "src/**"
- "package.json"
pull_request:
paths: *code_paths
jobs:
test:
runs-on: ubuntu-latest
services: &postgres
db:
image: postgres:18
env:
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432
steps:
- uses: actions/checkout@v6
- run: npm test
integration:
runs-on: ubuntu-latest
services: *postgres
steps:
- uses: actions/checkout@v6
- run: npm run test:integration
```
[Reusing workflow configurations](https://docs.github.com/en/actions/reference/workflows-and-actions/reusing-workflow-configurations)
is the source of truth for anchors. If a rule here disagrees with that page,
obey the page.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!