2025, Oct 15 14:00

How to Preserve YAML Anchors and Aliases in ruamel.yaml and Prevent Merge-Key id001 Auto-Renames

Learn why ruamel.yaml rewrites YAML anchors and aliases with the merge key, and how to keep anchors stable, preserve comments, and avoid id001 aliases.

Anchors and aliases in YAML are a powerful way to avoid duplication, but they can become fragile when tools rewrite them unexpectedly. If you use ruamel.yaml to update a document that relies on merges and anchors, you might see autogenerated alias names like id001 appear or anchors move to different nodes. Here is what happens and how to keep anchors untouched while still modifying the document.

Problem setup

Consider augmenting a YAML config while preserving comments and structure. The following Python snippet appends a new client to the clients mapping:

import ruamel.yaml
parser = ruamel.yaml.YAML()
parser.preserve_quotes = True
parser.indent(mapping=2, sequence=4, offset=2)
config_text = """---
# Some config file..
# This is the main clients config
clients:
  foobar1:
    name: foo Bar 1
    auth: false
    count: 1290
  barboz:
    name: BarraBoz
    auth: true
    count: 19
uids_default: &uids_default
  - attribute_email: mail
    datafile: vars1
uids:
  - <<: *uids_default
    skip: true
  - <<: *uids_default
    issuer: http://foo.bar
"""
new_client = {"adfs": 124423423, "123": "adsfadsfasdf", "name": "Dirk Van helst"}
doc = parser.load(config_text)
doc["clients"].insert(len(doc["clients"]), "vanhelst", new_client)
with open("/tmp/out.yml", "w") as out_f:
    parser.dump(doc, out_f)

The entry is added and comments are preserved, but the output also introduces automatically generated aliases and shifts anchors, e.g. replacing the original anchor with id001 and moving it from a sequence to the contained mapping.

What actually goes wrong

There is a single anchor and two aliases. The anchor is attached to a sequence that contains exactly one mapping. That sequence is then used as the value of the << merge key. When ruamel.yaml preserves merges, it flattens a single-element sequence, which moves the anchor from the sequence to the mapping inside it. This behavior stems from merge key handling that does not cope with aliases of anchored sequences. If the sequence had two elements, the merge entry would instead be rendered as a list of aliases after the merge key, for example:

  - <<: [*id0001, *id0002]

As-is, the single-item sequence is flattened, and the anchor appears to be “touched.”

Solution

Remove the unnecessary sequence around uids_default if you are not using that optional list. Anchor the mapping directly and keep the merge pointing to that mapping. With that change, ruamel.yaml preserves anchors and aliases as expected.

import sys
import ruamel.yaml
parser = ruamel.yaml.YAML()
parser.preserve_quotes = True
parser.indent(mapping=2, sequence=4, offset=2)
fixed_yaml = """# Some config file..
# This is the main clients config
clients:
  foobar1:
    name: foo Bar 1
    auth: false
    count: 1290
  barboz:
    name: BarraBoz
    auth: true
    count: 19
uids_default: &uids_default
    attribute_email: mail
    datafile: vars1
uids:
  - <<: *uids_default
    skip: true
  - <<: *uids_default
    issuer: http://foo.bar
"""
new_client = {"adfs": 124423423, "123": "adsfadsfasdf", "name": "Dirk Van helst"}
doc = parser.load(fixed_yaml)
doc["clients"].insert(len(doc["clients"]), "vanhelst", new_client)
parser.dump(doc, sys.stdout)

The resulting YAML keeps anchors and merges intact:

# Some config file..
# This is the main clients config
clients:
  foobar1:
    name: foo Bar 1
    auth: false
    count: 1290
  barboz:
    name: BarraBoz
    auth: true
    count: 19
  vanhelst:
    adfs: 124423423
    '123': adsfadsfasdf
    name: Dirk Van helst
uids_default: &uids_default
  attribute_email: mail
  datafile: vars1
uids:
  - <<: *uids_default
    skip: true
  - <<: *uids_default
    issuer: http://foo.bar

If you do need to merge multiple mappings, anchor each mapping with a different anchor and place the sequence of aliases as the value of the << merge key.

Why this matters

Configs that rely on YAML anchors, aliases, and the merge key often feed automation, CI pipelines, and code that depends on stable structure. Silent rewrites of anchors to id001-style aliases or relocating anchors can make diffs noisy and brittle, or worse, confuse downstream tools expecting a particular shape. Keeping merges attached to anchored mappings instead of single-element sequences avoids this rewrite.

Notes and outlook

This behavior is considered a bug in ruamel.yaml’s merge handling for aliases of anchored sequences. It should be fixed in the next release (> 0.18.14). Also, note the file extension choice: you are still using .yml; consult the YAML FAQ on yaml.org and follow its recommendation.

Conclusion

When using ruamel.yaml with merges, avoid anchoring a single-item sequence and then merging that sequence. Anchor the mapping directly so the merge key points to a mapping, not a list. If you need to merge multiple mappings, use distinct anchors and provide a sequence of aliases after <<. This keeps anchors stable, preserves comments, and prevents ruamel.yaml from introducing id001-style aliases during serialization.

The article is based on a question from StackOverflow by Dick Visser and an answer by Anthon.