#29739·argo-cd

bug: manual force-sync hard-replaces shared cluster-scoped resources via tracking-ID conflict, ignoring Prune=false

Author: nadidurnaCreated Sep 16, 2026Updated Sep 16, 2026
Labelsbugbug/severity:criticialbug/priority:urgentcomponent:sync

Checklist:

  • I've searched in the docs and FAQ for my answer: https://bit.ly/argocd-faq.
  • I've included steps to reproduce the bug.
  • I've pasted the output of argocd version.

Describe the bug

A manually-triggered sync with force: true performs a hard replace (kubectl replace --force) on a shared, typically cluster-scoped resource (e.g. a Namespace) when that resource's tracking label (app.kubernetes.io/instance) points to a different application than the one being synced. The resource is deleted and recreated, which for a Namespace means it enters Terminating and takes every workload inside it down.

Crucially, argocd.argoproj.io/sync-options: Prune=false does not protect against this: it only guards the prune code path, not the force-replace path.

This is not restart- or cache-timing specific. Unlike autoSync — which is gated behind an OutOfSync check in controller/appcontroller.go — a manual force-sync is not gated by that check, so the race can occur any time two apps declare the same resource and the non-owning app is force-synced.

Note: this issue supersedes and re-scopes #28630. The "partial cache after controller restart" framing in that issue was inaccurate — Path A (auto-sync + prune) is correctly guarded (autoSync only runs on OutOfSync; a failed live-state load yields Unknown, and EnsureSynced() holds the cache lock across the whole resync). The genuine gap is the force-sync + tracking-ID conflict described here. Thanks to @ivomarino for the code-level analysis.

To Reproduce

  1. Create App-A and App-B that both declare the same shared resource (e.g. Namespace/foo, or any cluster-scoped resource).
  2. Sync App-A first so the resource's app.kubernetes.io/instance label points to App-A (App-A owns the tracking ID).
  3. On App-B, set argocd.argoproj.io/sync-options: Prune=false on the shared resource.
  4. Trigger a sync for App-B with force: true (UI "Force" or argocd app sync App-B --force).
  5. Observe: ArgoCD issues a force replace on the shared resource because of the tracking-ID conflict. The resource is deleted and recreated (Namespace → Terminating), despite Prune=false.

No controller restart and no partial cache are required to reproduce.

Expected behavior

  • A force-sync must not hard-replace a resource owned (by tracking ID) by another application; a tracking-ID conflict should surface as a SharedResourceWarning / sync error instead of a destructive replace.
  • argocd.argoproj.io/sync-options: Prune=false should also protect against force-replace-triggered deletion — or the docs should explicitly state that Prune=false does not cover the force-replace path.

Version

bash
 argocd version: v3.3.9
 argocd-server: v3.3.9 (chart argo-cd-9.5.11)
 Kubernetes: GKE 1.30.x
 Controller mode: Deployment (sharded, multiple replicas)

Related: #28630 (original report, re-scoped by this issue)