## TL;DR
An ArgoCD sync stuck in `Progressing` means ArgoCD applied the manifests but the resources never reached a healthy state, or a hook/pre-sync step is hanging. Check the application's resource tree for which resource is not healthy, then look at that resource's events: usually a pod crash loop, a failing job hook, or a sync wave waiting on something that will never complete.

## Error / query
```text
ArgoCD sync stuck in Progressing: how to debug
```

## Use this skill when
- `argocd app get` shows `Sync: Progressing` for a long time with no movement
- The UI shows the app yellow/blue with resources stuck in `Progressing`
- A sync operation never completes or times out
- It worked before and now hangs after a manifest change

## Not for this skill when
- Sync status is `OutOfSync` (nothing applied yet; diff and sync instead)
- Sync status is `Unknown` or `Error` (ArgoCD cannot reach the cluster or the repo)
- Resources are healthy but the app shows degraded (health assessment config, not sync)
- You are debugging Git webhook delivery to ArgoCD (repo connectivity, not sync progress)

## Steps

### Step 1: See which resources are not progressing
```bash
argocd app get [app-name] --show-resources
```
Expected: the resource table shows per-resource health and sync state. One or two resources stuck in `Progressing` while the rest are `Healthy`/`Synced` tells you exactly where to look.

### Step 2: Check for hanging hooks and sync waves
```bash
argocd app get [app-name] -o json | python3 -c "
import json,sys
d=json.load(sys.stdin)
for r in d['status']['resources']:
    if r.get('hook') or 'wave' in str(r.get('info','')):
        print(r['kind'], r['name'], r.get('hook'), r['status'])
"
```
Expected: hooks (PreSync/Sync/PostSync jobs) that never completed block the whole sync. A PreSync hook pod in CrashLoopBackOff is the classic hang.

### Step 3: Inspect the stuck resource in the cluster
```bash
kubectl get [kind] [name] -n [namespace] -o yaml | grep -A 10 "conditions:\|status:"
kubectl describe [kind] [name] -n [namespace] | tail -30
```
Expected: the resource's own status explains the wait: a Deployment with unavailable replicas, a Job that never completes, a pod crash looping. Fix the underlying resource; ArgoCD is just reporting it.

### Step 4: Look at ArgoCD's sync operation details
```bash
argocd app history [app-name] | head -5
argocd app get [app-name] -o json | python3 -c "
import json,sys
op=json.load(sys.stdin)['status'].get('operationState',{})
print(op.get('phase'), op.get('message'))
"
```
Expected: the operation phase and message, e.g. `Running` with a sync-wave wait message. If the message names a specific resource or hook, go back to step 3 for that resource.

### Step 5: Terminate the stuck operation if the underlying issue is fixed
```bash
argocd app terminate-op [app-name]
argocd app sync [app-name]
```
Expected: the hung operation clears and a fresh sync runs to completion. Only terminate after fixing the stuck resource; terminating without a fix just re-hangs on the next sync.

## Variant phrasings

### "argocd sync never finishes"
Same thing. Steps 1-2 find the hanging resource or hook in most cases.

### "argocd application stuck syncing"
Check the operation state (step 4) for whether it is waiting on a resource, a hook, or ArgoCD itself.

### "argocd pre-sync hook hanging"
The hook job/pod is failing or never completes. Fix or delete the hook resource; consider `hook-delete-policy` so finished hooks clean up.

## Why it happens
ArgoCD's `Progressing` means "I applied everything, but the cluster has not converged to healthy yet." ArgoCD waits on Kubernetes status conditions, so a Deployment whose pods crash, a Job that never succeeds, or a hook that hangs all keep the sync in Progressing indefinitely. The sync is stuck because the desired state is not actually achievable, not because ArgoCD is broken.

## Edge cases and pitfalls
- Custom resources need a health check (lua script or `argocd-cm` config); without one ArgoCD may wait forever on a CR that is actually fine.
- Sync waves: a wave-N resource that can never become healthy blocks all higher waves silently; check wave annotations.
- `Sync` with `prune` on a resource another controller recreates causes a progress loop; check for fighting controllers.
- ApplicationSets generating duplicate resources confuse the sync state; verify the generated app list.
- Do not keep pressing sync on a hung operation; terminate it first (step 5), otherwise operations queue behind the stuck one.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_iCd027C0IP7Sq08R4mMlJw
