Skip to content
datahacks.dev
YAML

Comments in YAML and What Parsers Do With Them in Practice

YAML allows comments with a hash, but most parsers throw them away when they load and save. What survives a round trip in PyYAML, ruamel.yaml and yq.

6 min read
Summarize in AI
Flat illustration of a tree of nested objects beside the document it came from, with the YAML logo.

YAML is a format for people, and people want to say why a value is what it is. The language provides comments for that, and then most of the tools that read the file throw them away. Whether a comment survives depends on the library and on how you use it. This post states the syntax and shows, with real output, what three common tools do to comments when they load, change and save a file.

The syntax

A comment starts with # and runs to the end of the line. Two placements are common:

YAML
# Top-level comment about the whole file
server:
  host: db1      # trailing comment, explains this value
  # a comment on its own line, indented like the content
  port: 5432

The hash must be at the start of a line or preceded by whitespace to count as a comment. In the middle of a word it is just a character: url: http://x/#frag has a fragment, not a comment. There are no block comments, so a multi-line comment is one hash per line. Comments inside a block scalar (the content after | or >) are not comments at all: they are part of the text (YAML multiline strings with pipe and greater than).

Two accidents are worth knowing. A hash after a space inside a plain value ends the value: color: red #fff loads as red. That is the intended behavior, and it surprises people who meant the hash as part of the value; quote it ("red #fff"). And a comment line between list items or keys is ignored for structure, but its indentation is checked by linters, because a comment indented unlike its neighbors is usually a mistake (yamllint's comments-indentation rule reported "comment not indented like content" in the messy sample).

What PyYAML does: nothing

The scanner discards comments before the parser sees them, so the loaded Python objects contain none. Load and dump:

Python
import yaml
print(yaml.safe_dump(yaml.safe_load("# top\na: 1  # why\nb: 2\n"), sort_keys=False), end="")
Text
a: 1
b: 2

Both comments are gone, and sort_keys=False was needed to keep the key order, because the default dumper sorts keys alphabetically. If a program reads a configuration file with PyYAML, modifies one value and writes it back, every explanation a person wrote is deleted. For read-only use that is fine. For tools that edit files, it is a bug in the design.

What ruamel.yaml does: keeps them

ruamel.yaml's round-trip mode loads into special container types that remember comments, order, quote style and indentation:

Python
from ruamel.yaml import YAML
y = YAML()
d = y.load("# top\na: 1  # why\nb:   2\n")
d["b"] = 3
y.dump(d, sys.stdout)
Text
# top
a: 1  # why
b: 3

The top comment and the trailing # why survived, and so did the order. The extra spaces in b: 2 were normalized, since the value was rewritten. This is the tool to use when code edits human-maintained files (preserving comments and order with ruamel yaml goes through the settings: quotes, indentation, width). The round-trip loader's objects behave like dictionaries and lists, with a few additions for comments, so most code works unchanged.

What yq does: keeps them, with small changes

yq edits files in place and attaches comments to the nodes they belong to. On a Compose file with a trailing comment on one image line:

Text
< image: nginx:1.25   # pinned on purpose
---
> image: nginx:1.25 # pinned on purpose

The comment stayed, but the three spaces before it became one. yq was asked to change a different key and rewrote the spacing on an untouched line. In version control that appears as a changed line, and a reviewer has to read it to learn that nothing important changed. Other parts of the file kept their layout. If you run a tool across many files, expect some churn of this kind on the first pass, then commit it as its own "normalize" change so later diffs are clean (using yq to update many YAML files at once).

Rules for comments that last

Comment the why, not the what. port: 5432 # PostgreSQL default repeats the value. timeout: 30 # the vendor API drops connections after 60s saves a future incident.

Put information a program needs in a key. If a script has to read "this block is deprecated", make it deprecated: true. Comments are invisible to parsers by design, and the tools that delete them will delete it.

Use comments to document the contract. A block at the top that lists each key, its type and its default is cheap documentation that lives with the file. For validation, keep a schema (validating config YAML with JSON Schema).

Do not leave dead configuration in comments. Commented-out blocks drift from reality. Delete them and let version control remember.

Use round-trip tools for edits by program. If a script must change a value, use a tool that preserves comments, and test it on a copy with a diff before running it on a hundred files.

Comments in real workflows

Comments earn their keep in three places. At the top of a file, a short block says what the file configures, who owns it and where the schema lives, so that the next person is not guessing. Above a non-obvious value, a line says why it has that value and what would break if it changed, which is the part that no schema can hold. And beside a temporary change, a comment with a name and a date says when to remove it, so the workaround does not become permanent by accident.

Review them like code. A comment that disagrees with the value next to it is worse than none, because readers trust it. When a pull request changes a value, the reviewer should look at the comment on it, and when the comment is stale, ask for it to be changed in the same commit. Teams that treat comments as part of the configuration, with the same review, end up with files that explain themselves.

One last caution is about secrets and personal notes. Comments are plain text in version control and in every copy of the file. An old password left in a comment, a customer name or an internal URL is as exposed as one in a value. Scan for them as you would for keys (keeping secrets out of YAML config files).

The cluster hub is YAML syntax you need to stop guessing at. For comment practice in shared config, see comments that help the next person reading your YAML. Comments are specified in section 6.6 of the YAML 1.2.2 specification, and the round-trip behavior in the ruamel.yaml documentation. Format and inspect a file in the YAML formatter.

When comments cannot carry the information you need

A program must read it. Use a key. Comments are lost the first time a naive tool saves the file.

The file is machine-generated. Comments written by a generator vanish on the next generation. Document the generator, not the output.

The file is converted to JSON. JSON has no comments, so a conversion drops them (converting YAML to JSON with yq). If the explanation matters downstream, put it in a field.

Reviewers read only the diff. An important caveat buried in a comment far from the changed line may be missed. Put critical warnings next to the values they concern.

Also inTooling
Share

Keep reading

Get new articles by email

We send one email when a new article is published. No spam.

Unsubscribe in one click. No spam, ever.