ArgoCD Automation: 7 GitOps Drift and App-Health Failures That Go Unwatched
Open the ArgoCD dashboard and count the yellow. Eleven of forty apps show OutOfSync. Nobody is alarmed. Somewhere along the way the team agreed, silently, that eleven yellow apps is just what the dashboard looks like — and the day one of them means something, it will blend into the ten that don't.
That is the core failure mode of ArgoCD automation: the tool reconciles Git to cluster faithfully, but it cannot tell you whether a given OutOfSync is a harmless annotation drift or a production hotfix about to be silently reverted. So the status field decays. OutOfSync stops meaning "act now" and starts meaning "probably fine." Degraded scrolls past. The signal is there; the meaning has leaked out of it.
On any fleet running more than a handful of Applications, the same seven patterns show up every time. This guide covers each one — what it is, why it happens, one command or status field to detect it, and what it typically costs you in reconciliation toil. Most of the checks below use the argocd CLI or kubectl and take a few minutes each.
This is part one of a three-part series. Part two is a hands-on guide to ArgoCD's native sync policies, health checks, and CLI triage; part three covers automating GitOps drift triage with an AI agent.
1. Apps parked OutOfSync until the status means nothing
What it is. An Application shows OutOfSync for days or weeks. The diff is real but small — a label a mutating webhook adds, a replicas field the HPA owns, an annotation from another controller. Because it never gets resolved, it becomes background color. Then a genuinely important drift — a config map that no longer matches Git — lands in the same yellow pile and nobody notices.
Why it happens. Auto-sync is off for that app (deliberately or by neglect), or the diff is noise the team never taught ArgoCD to ignore. Clearing it takes judgment, so it never gets cleared.
How to detect it. List every app that is not synced, fleet-wide:
argocd app list -o wide | awk '$2 != "Synced" {print}'
Then look at how long each has been drifting — an app OutOfSync for weeks is a status that has already lost its meaning.
Typical impact. No dollar figure, but a real one: the longer apps sit yellow, the higher the odds a meaningful drift goes unactioned. This is alert fatigue with a Git backend.
2. Manual kubectl hotfixes that auto-sync silently reverts
What it is. An incident at 2 a.m. Someone runs kubectl edit deployment or kubectl scale to stop the bleeding. It works. Then, minutes later, ArgoCD's automated sync with selfHeal: true reconciles the live state back to Git — and quietly undoes the fix. The incident reopens with no obvious cause, because the change that caused it was a controller doing exactly its job.
Why it happens. selfHeal reverts any live divergence from Git. It has no notion of "this divergence was an intentional emergency fix." Under pressure, people reach for kubectl, not a Git commit.
How to detect it. Check whether an app self-heals, and inspect its recent sync activity:
argocd app get my-app -o json | jq '.spec.syncPolicy.automated'
argocd app history my-app
A selfHeal: true policy plus a fresh auto-triggered sync right after an incident window is the fingerprint of a reverted hotfix.
Typical impact. Extended incidents and eroded trust in GitOps — the "did ArgoCD just undo my fix?" moment that pushes teams to disable auto-sync entirely, throwing away its main benefit.
3. Drift that auto-sync does NOT catch when selfHeal is off
What it is. The mirror image of #2. With automated sync enabled but selfHeal: false, ArgoCD applies new Git commits but does not revert manual live changes. So a kubectl edit sticks — indefinitely — and the app reports OutOfSync while the cluster runs config that exists nowhere in version control. Your Git repo is no longer the truth.
Why it happens. Teams turn selfHeal off precisely to avoid pattern #2, then forget the trade-off: now every manual change silently persists, and the repo drifts out of alignment with reality.
How to detect it. Diff live state against Git for a suspect app:
argocd app diff my-app
Any hunk here is config running in your cluster that isn't in Git. If the app is OutOfSync and selfHeal is false, that drift will live forever until someone acts.
Typical impact. Silent configuration drift — the failure mode GitOps was supposed to eliminate. It surfaces months later as an un-reproducible environment or a disaster-recovery rebuild that comes back subtly wrong.
4. Degraded health that nobody drills into
What it is. An app's health rolls up to Degraded, but the top-level status is all anyone looks at. Underneath, a single Deployment is stuck below its desired replica count, or a Job is in CrashLoopBackOff, or an Ingress never got an address. The rollup is red; the reason is three clicks down, so nobody makes the clicks.
Why it happens. Health rolls up but root cause does not. Drilling into which child resource is unhealthy takes deliberate effort, and Degraded is common enough during deploys that people learn to wait for it to clear on its own.
How to detect it. Pull the per-resource health tree and filter to the unhealthy nodes:
argocd app resources my-app
argocd app get my-app -o json \
| jq '.status.resources[] | select(.health.status != "Healthy") | {kind, name, health}'
Typical impact. Slow-burn outages — a background worker that has been failing for a week, a canary that never went healthy — caught late because the rollup hid the specific resource that mattered.
5. Sync waves and hooks that fail halfway through
What it is. ArgoCD applies resources in ordered sync waves, and runs PreSync / PostSync resource hooks — a schema migration Job, a smoke test — between them. When a hook fails, the sync stops partway: some waves applied, later ones didn't. The app lands in a half-migrated state that neither matches the old version nor the new one.
Why it happens. Hooks are real workloads that can fail for real reasons — a migration timeout, a flaky test. The sync operation halts on failure by design, but the partial state it leaves behind is easy to miss if you only watch the final status.
How to detect it. Inspect the most recent sync operation, including hook results:
argocd app get my-app --show-operation
Look at the operation phase and each hook's status. A Failed phase with some resources Synced and others not means you're stuck mid-wave.
Typical impact. Failed or partial deploys that leave the app in an inconsistent state — the schema migrated but the new code never rolled out, or vice versa.
6. Orphaned resources after a chart refactor
What it is. You rename a resource, restructure a Helm chart, or drop a template. ArgoCD applies the new manifests, but the old Service or ConfigMap — no longer generated by the chart — keeps running in the cluster because pruning is off. It's orphaned: live, consuming a name or a port or an IP, referenced by nothing in Git.
Why it happens. prune: true is disabled by default because deleting things automatically feels dangerous. So refactors leave a trail of stragglers that accumulate release after release.
How to detect it. ArgoCD tracks orphaned resources when the feature is enabled on the project or app; you can also spot deletions in a dry-run diff:
argocd app get my-app -o json | jq '.status.resources[] | select(.requiresPruning == true)'
Any resource flagged requiresPruning is live in the cluster but absent from Git — an orphan waiting for someone to decide whether it's safe to remove.
Typical impact. Namespace clutter, stale Services still receiving traffic, and the occasional genuinely dangerous orphan — an old Ingress routing to a decommissioned backend.
7. App-of-apps sprawl nobody can reason about
What it is. The app-of-apps pattern — a parent Application that generates child Applications — scales beautifully until it doesn't. Dozens or hundreds of generated apps, several layers deep, and no single view of which ones are drifting, unhealthy, or stale. A child app's OutOfSync at the bottom of the tree is invisible from the top.
Why it happens. The pattern is designed to hide individual apps behind a parent, which is exactly what makes fleet-wide problems hard to see. There's no built-in rollup that says "3 of your 180 child apps are Degraded."
How to detect it. Get a fleet-wide count of every app's sync and health at once:
argocd app list -o json \
| jq -r '.[] | [.metadata.name, .status.sync.status, .status.health.status] | @tsv' \
| sort -k2
Scanning this instead of the dashboard surfaces the child apps buried under the parents.
Typical impact. Whole apps drifting or degrading unnoticed inside the tree — the more successful your app-of-apps rollout, the larger the blind spot.
Why the reconciliation loop keeps generating toil
Here's the uncomfortable part. ArgoCD's reconciliation loop is continuous and tireless — and that's exactly why the toil is continuous too. Every deploy can leave a partial sync. Every incident invites a kubectl hotfix that self-heal reverts or, worse, doesn't. Every chart refactor sheds an orphan. Every new app added to the app-of-apps tree widens the blind spot.
The seven patterns above aren't a backlog you clear once. They're a steady-state output of a system that is doing its job. And the tool gives you the raw signal — OutOfSync, Degraded, a diff, a failed hook — but not the judgment. Deciding whether a drift is a hotfix worth keeping or an accident worth reverting is human work, and it doesn't scale by staring at a dashboard harder.
The answer isn't to disable auto-sync or stop looking. It's to make the triage continuous — to have every OutOfSync diffed and classified the moment it appears, so the yellow on your dashboard means something again.
Make GitOps triage continuous
Everything above can be watched for you. CloudThinker agents connect to your ArgoCD API server read-only and watch sync and health across the whole fleet — diffing each OutOfSync against Git, classifying the drift (manual hotfix, controller mutation, chart bug), and proposing the safe action, with any actual sync gated behind your approval.
Try CloudThinker free — 100 premium credits, no card required — or continue the series with the native ArgoCD automation guide.
