I was debugging why a Kubernetes deployment wasn’t updating after merging a PR. The manifest was in git, but the cluster was still running the old version. Turns out ArgoCD was stuck in a sync loop. Here’s what I learned.

My GitOps workflow Link to heading

A typical ArgoCD workflow looks like this:

  1. Open PR with manifest changes (Helm chart, kustomize, or raw YAML)
  2. Merge to main
  3. ArgoCD detects the change (polls every 3 minutes by default)
  4. Auto-sync deploys to cluster (if enabled)

When auto-sync is disabled, I manually trigger syncs. This is common for production environments where you want explicit control.

Quick reference commands Link to heading

List applications:

kubectl get applications -n argocd

View application status:

kubectl get application my-app -n argocd -o yaml

Force refresh (fetch latest from git):

kubectl patch app my-app -n argocd \
  -p '{"metadata": {"annotations":{"argocd.argoproj.io/refresh":"normal"}}}' \
  --type merge

Get sync status as JSON:

kubectl get application my-app -n argocd -o json | \
  jq '.status.resources[] | select(.name == "my-resource")'

Check what’s out of sync:

kubectl describe application my-app -n argocd | less

ArgoCD CLI (easier when you have it) Link to heading

argocd app get my-app
argocd app sync my-app

But kubectl works when you don’t have the CLI handy or when you’re debugging from a pod without the ArgoCD binary.

Handling failed syncs Link to heading

The most common sync failures I see:

1. Resource quota exceeded Link to heading

error: namespaces "my-namespace" is forbidden: exceeded quota

Fix: Increase the ResourceQuota or delete unused resources. Check what’s consuming quota:

kubectl describe resourcequota -n my-namespace

2. Immutable field changes Link to heading

error: field is immutable

Fix: Some fields can’t be changed after creation (like Service clusterIP). You need to delete and recreate:

argocd app sync my-app --force

Or manually delete the resource and let ArgoCD recreate it.

3. Webhook validation failures Link to heading

error: admission webhook denied the request

Fix: Check the webhook configuration. Sometimes webhooks are too strict or have bugs. View the full error:

kubectl get application my-app -n argocd -o yaml | grep -A 20 "status:"

4. Git repository unreachable Link to heading

error: failed to get repository

Fix: Check ArgoCD’s access to your git repo:

argocd repo list
argocd repo get https://github.com/your/repo

SSH key might have expired, or the repository moved.

My debugging workflow Link to heading

When a sync fails:

  1. Check the application status in the UI (I know, but it’s actually good)
  2. Look at the sync operation details: argocd app get my-app
  3. Check the actual diff: argocd app diff my-app
  4. Inspect the affected resources: kubectl get/describe <resource>
  5. Check ArgoCD controller logs: kubectl logs -n argocd -l app.kubernetes.io/name=argocd-application-controller

The diff is crucial - it shows you exactly what ArgoCD is trying to change. Sometimes you find unexpected changes from overlapping apps or manual kubectl edits.

When to use sync vs refresh Link to heading

  • Refresh: Fetches latest from git but doesn’t apply. Use this to see what would change.
  • Sync: Applies the changes to the cluster.
  • Hard refresh: Clears the cache and re-clones the repo. Use when git force-push happened.
# Just check for changes
argocd app get my-app --refresh

# Apply changes
argocd app sync my-app

# Nuclear option - clear cache and sync
argocd app get my-app --hard-refresh
argocd app sync my-app --force

Auto-sync philosophy Link to heading

I’m opinionated here: enable auto-sync for non-production environments, disable it for production.

Why? In dev/staging, you want fast feedback. Merge and it deploys within 3 minutes. No manual steps.

In production, you want a human to click the sync button. It’s a forcing function to:

  • Verify the diff looks right
  • Check if it’s a good time to deploy
  • Be ready to rollback if needed

Configure it in the Application manifest:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: my-app
spec:
  syncPolicy:
    automated:
      prune: true  # Delete resources removed from git
      selfHeal: true  # Sync if someone does manual kubectl changes
    syncOptions:
      - CreateNamespace=true

Resources Link to heading