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

BreakingAdditive
Removing a key from a --json documentAdding a key
Changing a key's type or meaningAdding an optional flag
Removing or renaming a command or flagAdding a command
Changing a flag's default so an existing invocation behaves differentlyAdding a new TVO-* code for a genuinely new condition
Changing the exit code for an existing condition
Changing which TVO-* code an existing condition raises, or retiring one

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 --json and --quiet. They select mutually exclusive output modes and supplying both is rejected before dispatch, as a usage error.
  • --json wins over --format. --format json and --json produce identical output.
  • Colour is off automatically. JSON mode, a non-TTY, and the NO_COLOR environment variable each suppress styling — you do not need a flag for it.

Global flags worth knowing in a script

FlagEffect
--jsonThe structured contract on stdout.
-q, --quietSuppresses success and progress output. Errors still print.
--justification <text>A compliance reason recorded in signed audit metadata. Repository policy may require it before selected mutations.
--no-syncSkips the opportunistic auto-sync for one command — useful when a CI step must not touch the network.