thiagowfx's avatar

¬ just serendipity 🍀 (not just serendipity)

ArgoCD: sync stuck in terminating

• 299 words • 2 min • updated

Problem statement: An ArgoCD application’s sync operation is stuck in Terminating for hours. The web UI shows operation is terminating due to timeout. A PreSync hook resource keeps appearing as Running even though the underlying Job already completed in the cluster.

shell
% kubectl get application <app> -n argocd -o yaml | yq '.status.operationState.phase'
Terminating

The application controller is stuck in a busy loop:

"Resuming in-progress operation. phase: Terminating, message: operation is terminating due to timeout"
"No operation updates necessary to '<ns>/<app>'. Skipping patch"

In my case this followed a transient ImagePullBackOff on a PreSync hook job: the image was being mirrored to a regional ECR, the first hook attempt failed, and by the time the image landed and the retry succeeded, the parent operation had already moved into Terminating and never recovered.

The instinct is to clear the operation:

shell
kubectl patch application <app> -n argocd --type merge -p '{"operation": null}'

It’s a no-op. The wedge doesn’t live in spec.operation (the request) — it lives in status.operationState (the controller’s bookkeeping). And status is owned by the controller, so a regular --type merge patch won’t touch it.

We need the status subresource:

shell
kubectl patch application <app> -n argocd --type merge \
  -p '{"operation": null}'
kubectl patch application <app> -n argocd --subresource status --type merge \
  -p '{"status":{"operationState":null}}'

Both patches together. Neither touches cluster resources — they only modify ArgoCD’s view of the operation. After that, re-trigger the sync from the UI and the application reconciles cleanly.

Restarting argocd-application-controller does not help: the stuck state sits in etcd, and the freshly-elected leader picks it up on startup and resumes the same loop.

One caveat: verify the underlying hook resource has actually finished before clearing. kubectl describe job -n argocd <hook-job> should show 1 Succeeded. Otherwise we’ll lose in-flight progress.