argocd app diff: Preview Drift Before Sync
argocd app diff prints the difference between the live cluster state and the desired Git state for an Application.
Run diff on a pull request to show reviewers exactly what a sync would change. It is the GitOps equivalent of a plan step.
What it does
argocd app diff compares rendered manifests from the source against live resources and prints a unified diff. It exits 1 when there is a difference and 0 when in sync, which makes it usable as a drift gate.
Common usage
argocd app diff guestbook
# diff against a proposed revision (e.g. a PR branch)
argocd app diff guestbook --revision "$PR_SHA"
# diff against locally rendered manifests
argocd app diff guestbook --local ./manifestsOptions
| Flag | What it does |
|---|---|
| --revision <ref> | Diff against a specific Git revision |
| --local <dir> | Diff against locally rendered manifests |
| --hard-refresh | Force a fresh manifest generation |
| --exit-code | Return 1 on differences (on by default) |
In CI
Because diff exits 1 on any difference, wrap it in a step that captures the output but does not fail the whole job (for example pipe to a comment), or set continue-on-error. server-side fields like resource versions are normalized out, so the diff focuses on real changes.
Common errors in CI
A pipeline failing on diff is usually the exit code, not an error: exit 1 means drift exists. "rpc error: code = Unknown desc = Manifest generation error" means the source could not render (missing Kustomize/Helm or a bad path). "--local requires --server-side-generate" guidance appears when local diffing needs server-side rendering for that app type.
Using this in CI
A runner has no kubeconfig, no cached context, and no interactive auth. Every kubectl invocation in CI needs the context supplied explicitly, and most confusing CI failures here are the command running against the wrong cluster or no cluster at all.
# never rely on the ambient context on a runner
kubectl --context "$KUBE_CONTEXT" -n "$NAMESPACE" get pods
# confirm what you are actually connected to before mutating anything
kubectl config current-context
kubectl cluster-info
# fail fast instead of hanging on an unreachable API server
kubectl --request-timeout=30s get nodes