Skip to content

When a Comma Cuts the Description: YAML Under the Linter

Adityo Guni Waluyo

Eight of ten OpenAPI lint errors were not contract drift: an unquoted comma in a flow-mapping description became a bogus null-keyed entry.

TL;DR

Ten OpenAPI lint errors weren't contract drift but YAML parsing: unquoted commas inside flow mappings silently truncate description values. Linting grades the parsed debris, not the source, so Redocly flagged symptoms far below the real cause. Quoting descriptions fixed it; openapi-typescript regeneration produced byte-identical output, proving the fix was pure hygiene.

I was running the linter on the KotaPortal project's OpenAPI description when the terminal exploded with 10 structural errors. My first instinct was contract drift: someone added a field in a handler without updating the spec, or someone changed a response schema quietly. I opened the commit history looking for suspicious changes to response or parameter definitions.

Nothing. The only file touched was api/docs/openapi.yaml, and the latest change was hygiene, not contract. Digging deeper took me to a much lower layer: the problem was not in the OpenAPI, it was in the YAML parser.

The Comma Is Not Text

Of those 10 errors, 8 came from descriptions inside flow-mappings that contained a comma without quotes. Inside curly braces, a comma is not ordinary text. The YAML 1.2.2 specification names square brackets, curly braces, and the comma as indicators that "denote structure in flow collections" and are therefore forbidden in some cases to avoid ambiguity [1]. The consequence: a plain scalar inside a flow collection must be cleared of those structure indicators [1].

So when a description like description: digits, max 2 MB sits inside a mapping, the parser cuts at the first comma: the value becomes digits, and max 2 MB turns into a new mapping entry with a null key. The fragments survive as floating pieces parsed without any error. The YAML parser itself is perfectly happy, because by the grammar nothing went wrong. What breaks is the data that reaches the next layer, and the loss is silent: no warning, no failing line, just a sentence that suddenly stops mid-way.

The Linter Reads Debris, Not the Original Text

This is where my first assumption missed. Redocly CLI is indeed a tool "for working with OpenAPI descriptions ... including API linting, enhancement, and bundling" [2], but linting works on top of already-parsed data. When a Redocly rule is set to severity: error, "the API description doesn't pass validation" [3] once the rule fires. What gets graded is the truncated parsed result. The struct errors surface, but the cause happened several layers below, long before Redocly saw a single line of the file.

This also explains why the error count did not match the number of human mistakes. Ten errors, eight of them from the same repeating cause in description fields, and two others elsewhere. Had I stopped at 10 and started fixing them one by one without looking for the pattern, I would have edited text that was already semantically correct and still hit it.

Practically: quote every human sentence in descriptions. Double quotes restore exactly the value the machine already parsed, so this is a syntax correction, not a logic change. A description with commas inside a flow mapping stays legal for the parser; what is not guaranteed is that it survives intact.

The Proof: Byte-Identical Regeneration

To confirm this pass was purely hygienic, I regenerated with openapi-typescript, which turns OpenAPI 3.0/3.1 schemas into TypeScript types that are "runtime-free" [4]. The v1.d.ts file came out byte-for-byte identical, and git status was clean. No contract changed. The values the quotes restored were the values the parser had been reading all along; only the raw text is now immune to parser ambiguity.

The regeneration proof matters because it answers the most reasonable objection: what if quoting actually changes the description's content? If it did, the generated types would differ. In practice zero lines changed, and that also marks the boundary between two kinds of fixes that often get mixed into a single commit.

The remaining 17 warnings about 2xx/4xx response coverage were deliberately left alone. Documenting a 401 response adds entries to the generated types: one 401 measurably added 7 lines to v1.d.ts. That is a contract change, outside the scope of a hygiene pass. Fixing everything in one commit would destroy the evidence that the main fix changed nothing.

The lesson I took out of this investigation: a linter is an inspector, not a mechanic. If description text does not survive the parser, no validator can save it, and the first guess almost always blames the contract when only the syntax is broken.

Sources

Related articles