diff --exit-code: Fail CI on Stale Generated Files
diff returns exit status 1 when the two inputs differ, so a plain diff can fail a CI job the moment generated output drifts from what is committed.
GNU diff has no literal --exit-code flag (that is git diff); plain diff already sets the exit code. The pattern is the same: regenerate, diff against the committed copy, let a nonzero status fail the step.
What it does
diff exits 0 when the files are identical and 1 when they differ. Because CI steps fail on any nonzero exit, running diff against a freshly generated artifact turns "the committed file is stale" into a red build. The comparable Git command, git diff --exit-code, adds an explicit flag for the same behavior.
Common usage
# regenerate, then fail if it no longer matches what is committed
protoc --gen ... -o api.pb.new
diff -u api.pb api.pb.new # exit 1 fails the step
# check formatting is applied (gofmt example)
diff -u <(gofmt -d .) /dev/null # any diff output means unformatted filesOptions
| Flag / behavior | What it does |
|---|---|
| exit 0 | Files are identical |
| exit 1 | Files differ (fail the CI step) |
| exit 2 | Trouble: a file is missing or unreadable |
| -q | Report only whether files differ, no per-line output |
| -u | Show the unified diff so the failure log explains the drift |
In CI
The staleness gate is: regenerate the artifact into a temp path, then diff -u committed generated. Keep -u so the job log shows exactly what changed and a developer can copy the fix. For lockfiles, run the install then diff the lockfile against HEAD. For a repo-wide check across tracked files, git diff --exit-code is usually simpler.
Common errors in CI
The confusing case is a green build that should be red: if you swallow the exit code with || true or pipe diff into another command, the failure is lost; keep diff as the last command in the step. "diff: extra operand" means you passed three paths (often an unquoted glob). Exit 2 (missing file) is not a content difference; do not treat it as "stale".