The fix for argoproj/argo-cd#29476 ships in Argo CD v3.6.0-rc4. In that bug, a sharded application controller learns about a cluster from an update event instead of an add, never assigns it a shard, and the replica that should own it never reconciles its Applications. The fix was backported to 3.6 only. Below: which releases have it, how to tell whether you are hit, and what to do on 3.5 and earlier.
Which releases have it
| Release | Has the fix |
|---|---|
| v3.6.0-rc4 (2026-10-22) and later 3.6 releases | yes |
| v3.6.0-rc1 to rc3 | no |
| 3.5.x, through v3.5.6 | no |
| 3.4.x, through v3.4.12 | no |
| 3.3.x, through v3.3.18 | no |
v3.6.0 GA is scheduled for November 3, 2026 in the
release schedule.
The change is #29477 on master and
#30318 on release-3.6.
Check the controller image you run (use deployment instead of statefulset if you run the controller as a
Deployment for dynamic cluster distribution):
| |
If you build from a fork, look for the new helper:
| |
No match means you still have the old Update path.
Are you exposed
Only with more than one application-controller replica. With one replica every cluster belongs to shard 0, so the fallback below changes nothing.
Before the fix, Update stored the cluster and recomputed the distribution only when the ID, server or shard
changed between the event’s old and new objects:
| |
If the cluster was not in the cache yet, it was stored with no entry in Shards. IsManagedCluster treats a
missing entry as shard 0, so the shard-0 replica processes the cluster and its real owner skips it. An explicit shard in
the cluster secret does not help, because updateDistribution is what applies it, and it did not run. The cluster
stays that way until something else recomputes the distribution: another cluster added, removed or re-sharded, an
Application added or deleted on that replica, or a controller restart.
The upstream e2e test reaches that state like this:
- Create a cluster secret whose
configdoes not parse. The add event is dropped. - Fix the
config. That update is dropped too, because its old object still does not parse. - The next update, such as a label change or the informer’s periodic resync, is the first event the sharding cache sees for the cluster.
The comment on the fix names one more path: an update for a server whose cached entry was removed when another secret for the same server was deleted.
To look for it, grep every controller replica for the warning it logs when a cluster has no entry:
| |
Then list the Applications that were reconciled longest ago:
| |
An Application whose reconciledAt is far older than timeout.reconciliation is not being reconciled, whatever
its sync and health status say.
Until you can upgrade
- Restart the controller. At startup
Initcomputes every cluster’s shard from the full cluster list:1kubectl -n argocd rollout restart statefulset/argocd-application-controller - Create cluster secrets valid in one step, with
argocd cluster addor a manifest you have validated. A secret created broken and fixed later is the path the e2e test reproduces. - Alert on the warning and on stale
reconciledAt. Neither the sync status nor the health status changes when a cluster loses its owner.
What the fix changes
Add and Update now share one helper that compares the incoming cluster against the cached entry, not against the
event’s old object:
| |
The cache is keyed by server, so the event’s old object can belong to a different secret for the same server, or the
cluster may not be cached at all. membershipChanged covers an Update that moved a cluster to a new server URL and
removed the old entry. For round-robin and other index-based algorithms, adding a cluster can move other clusters to
a different shard, so a recompute on every membership change is required, not an optimization.
Running the upstream e2e test on kind
The test is TestShardsReconcileClusterFirstSeenOnUpdate in test/e2e/sharding_test.go
(#30227). It is on master; release-3.6 got only the fix.
| |
The test restarts the controller as shard 1 of 2 and the secret requests shard 1, so the result does not depend on the sharding algorithm. It uses shard 1, not 0, because shard 0 is where an unassigned cluster lands anyway.
What broke
make start-e2e-localchanges your current context’s namespace. It runskubectl config set-context --current --namespace=argocd-e2e. PointKUBECONFIGat a file that holds only the kind cluster, so your other contexts stay as they are.- Port 6379 was taken. The Procfile passes
--redis localhost:${ARGOCD_E2E_REDIS_PORT:-6379}to the controller, API server and repo server, so setARGOCD_E2E_REDIS_PORT=16379if another Redis already runs locally.ARGOCD_REDIS_LOCAL=truestarts a localredis-serverinstead of a container. - Timeouts that had nothing to do with the test. With other containers filling the Docker VM’s memory, the kind control plane swapped, the controller-manager lost its lease, and tests failed with service-account token and TLS handshake timeouts. Stop the other containers before a run.
- Proving the test fails without the fix.
git revert --no-commit <fix>followed bygit checkout -- controller/keeps the revert, becausecheckoutrestores from the index and the index holds the staged revert. Apply the reverse diff to the working tree instead.start-e2e-localruns the binaries indist/by default (ARGOCD_BIN_MODE=true), so rebuild after each change:1 2 3 4git diff <fix> <fix>^ -- controller/sharding | git apply make cli-local # run the test: expect a sync timeout git checkout HEAD -- controller/sharding && make cli-local - A retry loop instead of a pinned shard. The first version of the test recreated the cluster secret until
round-robin, which orders clusters by secret UID, put it on shard 1. Review asked for an explicit
shardin the secret instead. The test got shorter and stopped depending on the algorithm.
Background: Argo CD said Synced and Healthy for 57 hours while nothing deployed and what six weeks of argo-cd review looked like. Issue #29476 · fix #29477 · 3.6 backport #30318 · e2e #30227.