Product

Automating ArgoCD GitOps with an AI Agent: From OutOfSync Noise to Safe Reconciliation

selfHeal reverted a 2 a.m. hotfix and reopened the incident. Here's how CloudThinker connects to ArgoCD read-only, classifies each OutOfSync as a manual hotfix, controller mutation or chart bug from the diff and field ownership, and proposes the safe action. Writes go through Auto Mode, and a hotfix is never silently reverted.

9 min read
Cover Image for Automating ArgoCD GitOps with an AI Agent: From OutOfSync Noise to Safe Reconciliation

Automating ArgoCD GitOps with an AI Agent: From OutOfSync Noise to Safe Reconciliation

Someone ran a kubectl patch at 2 a.m. to stop the bleeding during an incident. It worked. The pods stabilized, the pager went quiet, and nobody wrote it down. At 2:03, ArgoCD noticed live state no longer matched Git, flipped the app to OutOfSync, and, because selfHeal was on for that project, reverted the hotfix to the broken manifest. The incident reopened. That's ArgoCD automation working exactly as configured and doing exactly the wrong thing. Auto-sync reconciles state. It has no idea whether the drift it just erased was an accident or the only thing keeping production up.

Part one mapped the failure patterns: apps parked OutOfSync until the status lost its meaning, drift from manual hotfixes, Degraded health nobody drills into, sync waves failing halfway, orphans after chart refactors. Part two built the native toolkit: sync policies chosen deliberately, sync windows, Lua health checks, argocd app diff, history and rollback, hooks, and ignoreDifferences.

Part two ended on its honest ceiling. selfHeal reverts every drift or keeps every drift. It can't tell a hotfix worth committing from an accident worth reverting. That judgment (the diff, the classification, the safe action) is still human work, and it comes back with every drift event. This part covers the layer that does it: CloudThinker triaging drift and app health continuously, proposing the safe reconciliation, and tracking deployments across sync waves. ArgoCD stays exactly where it is.


Why rules stop where judgment starts

Every native feature from part two is a rule written in advance. selfHeal is a rule. ignoreDifferences is a rule. A notification trigger is a rule. Rules work for the drift you predicted.

Real drift doesn't announce its category. An OutOfSync on orders-worker could be a 2 a.m. fix, an HPA doing its job, or a Helm values change that rendered something nobody intended. Each one needs a different action, and you only find out which by looking at evidence: the diff, who wrote the field, what Git did recently. An engineer does that naturally. A rule can only take the branch someone drew for it.

Connecting ArgoCD: an API token, about five minutes

01GitOps

Argo CDArgoCD

  • Argo CDApplications and projectsSync status, health, history, diffs
  • KubernetesManaged resourcesResource tree, events, rollout state
  • GitHubSource repositoriesCommits behind each synced revision

Read-only RBAC role

02Triage

CloudThinkerCloudThinker

  • Watch the fleetEvery OutOfSync and Degraded, every app
  • Classify the driftHotfix, controller mutation or chart bug
  • Propose the actionCommit, sync, ignore rule or rollback

No controller changes

03Where the team works

Chat and review

  • SlackSlack or TeamsFindings and approvals in the thread
  • GitHubPull requestsHotfixes codified for normal review
  • JiraFollow-ups with the evidence linked

Git stays the source of truth

CloudThinker reads the same API the argocd CLI uses. Changes reach the cluster the GitOps way: through Git, after review.

CloudThinker connects the way your security team would want any external reader to: a dedicated local account (or an SSO-mapped one) with a scoped API token, granted a read-only RBAC policy on the ArgoCD API server. That means get on applications, projects and repositories, nothing more. No write access at connection time, no controller changes, no admission webhook.

A minimal grant looks like this:

# argocd-cm
data:
  accounts.cloudthinker: apiKey
---
# argocd-rbac-cm
data:
  policy.csv: |
    p, role:cloudthinker-readonly, applications, get, */*, allow
    p, role:cloudthinker-readonly, projects, get, *, allow
    p, role:cloudthinker-readonly, repositories, get, *, allow
    g, cloudthinker, role:cloudthinker-readonly

Then generate the token and paste it, with the API server URL, into CloudThinker:

argocd account generate-token --account cloudthinker

The first scan runs against the same API the argocd CLI uses in part two, so the evidence you get back is the evidence you'd have gathered by hand with argocd app get and argocd app diff. The ArgoCD connection guide lists the exact RBAC lines for your review record.


What happens when an app drifts

01Signal

OutOfSync

An app drifts or goes Degraded. CloudThinker picks it up on the next poll.

02Evidence

Diff and history

The rendered diff, field managers, recent commits and sync operations.

03Judgment

Classify

Hotfix, controller mutation or chart bug, with the evidence for the call.

04Output

Proposal

Commit the hotfix, sync, add an ignore rule, or roll back. Never a silent revert.

Step three is the one selfHeal skips. It is also the one that decides whether a sync helps or hurts.

Auto-sync answers "does live match Git?" with yes or no. CloudThinker answers the question that decides what to do: why don't they match? For each OutOfSync app it pulls the same rendered diff the CLI produces, and classifies it.

  • Manual hotfix. A live-only change with no matching commit: an image tag bumped by hand, a replica count patched, an env var added during an incident. Reverting it silently is the wrong move. The finding is "this is a hotfix, here's the diff, decide whether to commit it or roll it back", not a sync.
  • Controller or mutation drift. A field an HPA, mutating webhook or operator owns: spec.replicas under an HPA, an injected sidecar, a defaulted imagePullPolicy. It isn't a deviation from intent. The long-term fix is a narrow ignoreDifferences rule, not a sync and not an alert on every reconcile loop.
  • Chart or template bug. The rendered manifest changed because a Helm chart bump or Kustomize edit produced something unintended: an orphan after a refactor, a renamed selector, a dropped label. Here the drift is real and Git is wrong. Syncing would spread the mistake.

Each classification arrives with its evidence: the diff hunk, the last synced commit, the field manager that wrote the value, and which side, Git or live, holds the intended state.

One drift, investigated

Here's a single case end to end. The details are illustrative.

At 02:31, orders-worker in production goes OutOfSync. The project runs automated sync with selfHeal: false, so nothing reverts it. CloudThinker picks it up and starts with the three readings above.

The diff. One hunk: the container image is orders-worker:1.8.3 live and 1.8.2 in Git. Image tags aren't controller-owned, so reading two is unlikely.

Who wrote it. The Deployment's managedFields show a kubectl-patch manager writing the image at 02:07. A person, not a pipeline.

What Git did. No commit to the orders-worker values in four days, so reading three is out. The incident channel shows INC-4412 opened at 01:50: connection leak in orders-worker. Release notes for 1.8.3 mention a connection-pool fix.

The finding, posted in the incident thread:

orders-worker (prod) is OutOfSync: manual hotfix, keep it. Live runs 1.8.3, patched by hand at 02:07 during INC-4412. Git still pins 1.8.2, the version with the connection leak. Syncing now would reintroduce the incident. Proposed: a pull request setting image.tag: 1.8.3 in values-prod.yaml. After merge the app returns to Synced with no rollout. Not proposed: a sync, or a rollback.

The on-call approves. The pull request goes through normal review. The next person to enable selfHeal on that project doesn't undo last night.

Drill into Degraded health, not around it

An app can be Synced and still Degraded, and a Degraded nobody opens is a core waste pattern from part one. When health drops, CloudThinker inspects the Kubernetes state behind the rollup: availableReplicas against desired, CrashLoopBackOff reasons and last exit codes, failing readiness probes, a rollout stuck past its progressDeadlineSeconds, a PVC pending on a missing StorageClass. The finding is the resource-level cause, not the color of the tile.

Track deployments across sync waves

When a sync stalls, CloudThinker identifies the wave it stopped in and why: a PreSync hook that failed, a wave whose resources never went Healthy so the next never started, an app-of-apps child left behind. "Which commit is live in prod, and where did last night's sync stall?" becomes a question you ask in chat instead of a walk through argocd app history across a dozen apps.


Nothing syncs outside the controls you set

Findings imply actions. CloudThinker prepares the specific action with evidence (sync this app because the drift is a chart bug and Git is right; commit this hotfix; add this ignoreDifferences block for the HPA-owned field; roll back to the last healthy revision). It acts within Auto Mode, so you decide what runs on its own and what waits for approval.

One behavior always holds: CloudThinker never reverts a manual hotfix on its own. A live-only change is escalated as a keep-or-revert decision with the diff attached. Everything lands in an audit trail: what drifted, how it was classified, what was proposed, who approved, what synced, and when.


What a first pass typically finds

These numbers are illustrative, for a mid-market setup of roughly 90 Applications across 3 clusters, with app-of-apps in a couple of projects. Yours will differ.

FindingDetailTypical impact
Stale OutOfSync12 apps OutOfSync for 30+ days, status long ignoredReal drift invisible in the noise
Manual hotfix drift4 live-only patches with no matching commit, 1 in prodselfHeal would silently revert an incident fix
Controller-owned noiseHPA and sidecar-injected fields flagged on every loopConstant false drift, candidates for ignoreDifferences
Degraded, undrilled3 Synced-but-Degraded apps, 1 stuck past progressDeadlineSecondsBroken workload behind a green sync status
Stalled sync waves2 syncs halted on a failed PreSync hook, later waves never ranPartial deploys, no rollback triggered
Orphaned resources9 resources left after a chart refactor, no longer in GitDrift, cost and confusing diffs

Almost none of it is exotic. It's the part-one inventory, surfaced automatically, and each row arrives with a classification and a proposed action that waits for approval.

Why this beats the traditional setup

selfHeal and notificationsWith CloudThinker on top
A 2 a.m. hotfixReverted, or kept with no recordClassified as a hotfix, committed through review
Controller noisePermanent yellow until someone writes a ruleFlagged with the exact ignoreDifferences block
Degraded appA Slack message with a colorThe failing resource and its cause
Stalled syncFailed phase, dig through historyThe wave, the hook and what's half-applied
ActionsWhatever the policy flag says, every timeProposed with evidence, gated by Auto Mode, audited

The deeper difference is where judgment lives. Native automation encodes a decision once and applies it to every drift. CloudThinker makes the decision per drift, from evidence, and writes the reasoning down so you can check it.


Prompts to try in your first session

Ask in plain language. Each answer cites the ArgoCD data behind it (the same app get, app diff and app history evidence from part two), so you can verify it rather than trust it.

"Why is payments-api OutOfSync? Diff Git against live, tell me whether it's a manual hotfix, controller drift or a chart bug, and show me the diff. Propose only, change nothing."

"Which apps are Synced but Degraded right now? For each one, drill into the Kubernetes resources and tell me the actual cause, not just the health status."

"Walk me through last night's sync of the platform app-of-apps: which wave stalled, which hook failed, and which commit is live in prod versus what's in Git."

"List every OutOfSync app whose only drift is a field an HPA or webhook owns, and draft the narrowest ignoreDifferences rule for each."


What the agents won't do

"AI with access to your deploy system" should raise these questions. Here are the answers.

  • Read-only by default. The API token grants no sync or write permission. Triggering a sync, opening a pull request or changing an app spec needs scoped write access you grant explicitly, and still goes through Auto Mode.
  • No silent reverts of hotfixes. CloudThinker never reconciles away a live-only change the way selfHeal does. A hotfix is escalated as a keep-or-revert decision with the diff attached.
  • No invisible changes. Every proposal and execution is in the audit trail with its evidence and approver.
  • No architectural decisions. It will show you that a chart refactor orphaned nine resources. Whether to delete them or restructure the app-of-apps is an engineering call, and it stays yours.

Keep the part-two setup. Sync policies, windows, health checks and ignore rules are the right tool for the paths that are already deterministic. Put the agent on the part that never was.

Closing the loop

Part one showed where GitOps drifts. Part two showed how far sync policies, health checks and ignoreDifferences go, and that they stop at intent. What's left is the per-app decision of whether a drift is a hotfix to keep or an accident to revert. Teams that move from ignored OutOfSync columns to continuous drift triage typically cut time-to-answer on a drifted or Degraded app from a terminal session to under a minute, and stop letting selfHeal make decisions it was never qualified to make.

Related reading

Try CloudThinker free with 100 premium credits and no card required, and follow the ArgoCD connection guide to see your own first findings within the hour.

argocdcicdgitopskubernetesaiagentcloudthinker