Skip to content

Errors and Debugging

yq's failures are fewer and quieter than jq's, which is not entirely a compliment: where it does fail it says so plainly, but two of the most dangerous outcomes in this guide aren't failures at all. This chapter collects the real errors first — each produced by the command shown, on yq v4.54.1 — then the non-errors that deserve your suspicion, then the defensive patterns.

The geography, as with jq: results go to stdout, errors to stderr. In a pipeline, only the results flow onward; an errored yq in the middle of a pipe leaves the downstream tool holding nothing, which is why noticing exit codes matters.

The failure classes

Unlike jq's several exit codes, everything in this table exits 1:

Failure Exit
File can't be opened (missing, unreadable) 1
Input won't parse as the named format 1
Expression won't compile (bad syntax, unknown operator) 1

The file isn't there

$ yq '.services.web.image' nope.yaml
Error: open nope.yaml: no such file or directory

Plain, complete, and the first thing to check when a script's yq step "does nothing": the relative path is being resolved from a different working directory than you think.

The YAML is broken

$ yq '.server.port' broken.yaml
Error: bad file 'broken.yaml': go-yaml load error in scanner at L4.C8: mapping values are not allowed in this context

broken.yaml lost the space after port:'s colon and gained an indent problem; the scanner points at line 4, column 8 — the start of the line it couldn't make sense of — and names the rule it broke. YAML parse errors are positional for the same reason JSON's are: the parser knows exactly where the structure stopped working. Fix the file, not the expression.

The expression is broken

Two shapes. A syntax error — an unclosed bracket:

$ yq '.services[' compose.yaml
Error: bad expression, could not find matching `]`

And an unknown operator, reported by the lexer with the position inside the expression and the tail of your text it choked on:

$ yq '.tags | frobnicate' settings.yaml
Error: 1:9: lexer: invalid input text "frobnicate"

Chapter 3 met this class twice — ascii_upcase and getpath, borrowed from jq, dying at columns 15 and 1 respectively. The fragment quoted can look mangled ("cii_upcase)" — that's your text from the failure point, not a typo in the error). Read it as "the first thing I didn't recognise starts here," and it usually names the culprit.

The non-errors

Missing paths answer null

$ yq '.missing.deep.path' compose.yaml
null

Exit 0. No complaint at any depth: yq walks as far as the document goes and reports null for the rest. For interactive use that's pleasant. In a script it's the failure mode that ships — a mistyped path becomes a null that becomes a broken deploy two steps later, with every exit code green. The defences are in the next section; the awareness is most of it.

+ is not always arithmetic

$ yq '.services.web.image + 1' compose.yaml
fieldnotes/web:1.4.21

You might have wanted a type error. What you got is string concatenation: + joins strings, and a number standing next to a string joins as text. 1.4.2 met 1 and produced 1.4.21, exit 0. yq's arithmetic is dynamically typed in a way that prefers producing something — which makes tag checks (chapter 8) part of writing any expression whose inputs you don't control.

Defensive patterns

Two operators carry most of the defensive weight. has asks whether a key exists, as a real boolean you can branch on:

$ yq '.services | has("web")' compose.yaml
true

And the alternative operator // replaces a missing or null result with a default, at the point of use:

$ yq '.services.web.missing // "not set"' compose.yaml
not set

Between them, most production expressions end up shaped like: select what exists (has, select), and never let a bare path's null travel further than the expression that asked for it (//). Add the habit from the errors above — when a pipeline step surprises you, rerun the yq command alone, read stderr, and check the exit code the pipeline was hiding — and you have this tool's whole debugging discipline. It is shorter than jq's, because yq fails less often. Stay suspicious of the silence anyway: in this tool, the dangerous outcomes are the ones that print something plausible and exit 0.