Output Formats#
diffyml supports eight output formats. Pick one with -o / --output.
| Format | Flag | Use case |
|---|---|---|
| detailed | -o detailed (default) | Human review — full context |
| compact | -o compact | Quick scan of changes |
| brief | -o brief | Counts only |
| github | -o github | GitHub Actions annotations |
| gitlab | -o gitlab | GitLab Code Quality JSON |
| gitea | -o gitea | Gitea CI annotations |
| json | -o json | Machine-readable, scriptable |
| json-patch | -o json-patch | RFC 6902 JSON Patch |
detailed (default)#
Human-readable terminal output with colors, paths, and surrounding context. Best for interactive use.
diffyml old.yaml new.yamlcompact#
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.yamlbrief#
Just the change counts. Useful when you only care whether something changed, not what.
diffyml -o brief old.yaml new.yamlPair 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.yamlTo 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-linesof 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.jsonUnlike 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.yamljson#
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