The --json output contract
Why it is safe to build on, and what may change under you.
Written By Dustin
Last updated About 3 hours ago
Every command accepts --json and emits a single structured object to stdout. That shape is a versioned surface with a published compatibility contract — the same category of promise as the storage format and the wire protocol, not a convenience that drifts between releases.
The shape
Success carries a top-level result; failure carries error with code, title, cause, exit_code, and context. No color, no glyphs, no prose outside the shape.
What counts as breaking
A breaking change to this surface tracks the release major and carries a deprecation period of at least six months and at least one minor release, during which both behaviours work. The compatibility and deprecation specification in the documentation is the system of record; this table is the summary you need to design a parser against.
Codes are never recycled
A TVO-<AREA>-<NNN> code is permanently bound to its meaning. A retired code is burned, not reissued with a new meaning — so a check for a specific code cannot silently start matching something else.
Migration notices are never in the document
During a deprecation window the old path keeps working and emits a notice naming its replacement and the earliest release in which removal may occur. That notice goes to stderr, never into the JSON document. A parser that reads stdout will never trip over it — and a pipeline that captures stderr will see the warning in time to act on it.
Practical rules
- Read stdout, watch stderr. They carry different things by design.
- Ignore unknown keys. New keys are additive and will appear.
- Branch on
error.code, not on text. Message wording is presentation. - Do not combine
--jsonand--quiet. They select mutually exclusive output modes and supplying both is rejected before dispatch, as a usage error. --jsonwins over--format.--format jsonand--jsonproduce identical output.- Colour is off automatically. JSON mode, a non-TTY, and the
NO_COLORenvironment variable each suppress styling — you do not need a flag for it.