Output Formats#

diffyml supports eight output formats. Pick one with -o / --output.

FormatFlagUse case
detailed-o detailed (default)Human review — full context
compact-o compactQuick scan of changes
brief-o briefCounts only
github-o githubGitHub Actions annotations
gitlab-o gitlabGitLab Code Quality JSON
gitea-o giteaGitea CI annotations
json-o jsonMachine-readable, scriptable
json-patch-o json-patchRFC 6902 JSON Patch

detailed (default)#

Human-readable terminal output with colors, paths, and surrounding context. Best for interactive use.

diffyml old.yaml new.yaml

compact#

One-line-per-change format. Good when you want a quick scan and don’t need surrounding YAML context.

diffyml -o compact old.yaml new.yaml

brief#

Just the change counts. Useful when you only care whether something changed, not what.

diffyml -o brief old.yaml new.yaml

Pair with --summary to swap the bare counts for an AI-generated description (see AI Summaries).

github#

Emits GitHub Actions workflow commands so changes show up as inline annotations on the PR diff.

diffyml -o github old.yaml new.yaml

To avoid spamming the UI, output is capped at 10 annotations per type. Combine with -s to fail the workflow when drift is detected.

Every difference becomes exactly one annotation, so multiline values are kept bounded:

  • A changed multiline value (a ConfigMap block scalar, an embedded values.yaml) is rendered as a line diff showing only the changed lines plus --multi-line-context-lines of context on each side. Every other run of unchanged lines collapses into a [N lines unchanged] marker. Collapsing only removes unchanged lines, so the resulting diff is capped at 40 lines to bound a value whose lines all changed.
  • Any other value — added, removed, unchanged, or a changed pair that is not a multiline string — has nothing to diff against, so each value is truncated to its first 20 lines.

Both caps append a [N more lines] marker counting the lines of the value that were dropped, so a hidden [24 lines unchanged] run counts as the 24 lines it stands for. The (N inserts, M deletions) header always counts the whole change even when the diff below it is truncated.

Individual lines are capped too, at 500 characters with a [N more characters] marker. Line counts alone bound the wrong dimension: a minified JSON blob or a last-applied-configuration annotation is a single line of any size, so it clears every line cap untouched. The count is in characters rather than bytes, so a multi-byte character is never cut in half.

Finally, the assembled message is capped at 4000 characters. The two caps above bound lines and line width separately, but not their product — 40 lines of 500 characters is 20,000, which the percent-encoding below can triple. It is also the only bound on the parts of a message that are not value lines: a difference’s path is a document key of any length. In practice it never fires on a readable annotation, and GitHub truncates the rendered message well below it.

A value rewritten beyond recognition — more than 80 lines of difference between the two sides — is not diffed. Producing a line diff costs memory quadratic in how different the two values are, and past that point the diff no longer fits in the 40-line cap anyway. Only wholesale rewrites reach it: a long value with a few changed lines is diffed however long it is.

Such a value is still reported as a multiline change rather than as a plain changed from … to …. The annotation carries a (rewritten, more than 80 lines differ) header, then each side truncated on its own — the before side marked -, the after side +. What the ceiling costs is the alignment between the two sides and the exact insert/deletion counts, not the values themselves.

PEM certificate values are replaced by the same one-line Certificate(CN=…, Issuer=…, Valid=…, Serial=…) summary the detailed output uses, so a rotation reads as one changed line instead of a diff of base64. Pass --no-cert-inspection to see the raw PEM (still subject to the caps above).

Annotation text is percent-encoded per the workflow command spec (%%25, \r%0D, \n%0A). GitHub renders the escapes as line breaks in the annotation; a raw newline would instead terminate the command and spill the rest into the build log. Property values such as file= additionally encode :%3A and ,%2C, since those delimit the property list — an unescaped comma in a path would swallow the annotation title.

The caps above apply to the message only. file= is escaped but never truncated: GitHub matches it against the files in the diff to attach the annotation, so a shortened path is a wrong path that would attach to nothing — or to another file that happens to match it. A long path costs a long line and nothing else.

gitlab#

Emits a GitLab Code Quality JSON report. Surface the report as a Code Quality artifact and GitLab will render diffs in the merge request UI.

diffyml -o gitlab old.yaml new.yaml > gl-code-quality.json

Unlike the GitHub annotations above, descriptions here are not truncated. The report is JSON, so an embedded newline is escaped rather than terminating anything, and each entry’s fingerprint is a hash of its description — bounding a description would change every fingerprint, making GitLab re-report existing findings as new, and two values sharing a truncated prefix would collide onto one fingerprint.

gitea#

Emits annotations in Gitea’s GitHub-Actions-compatible format.

diffyml -o gitea old.yaml new.yaml

json#

Machine-readable JSON: a top-level array of {path, type, from, to, document_index} objects (with file added in directory mode). type is one of added, removed, modified, order_changed. Pipe into jq for scripted processing.

diffyml -o json old.yaml new.yaml | jq '.[] | select(.type == "modified")'

json-patch#

RFC 6902 JSON Patch — a sequence of add/remove/replace operations that, when applied to from, produce to. Useful for replaying changes programmatically.

diffyml -o json-patch old.yaml new.yaml