Description
Summary
With global.singleArgoCD: true, internal/controller/pattern_controller.go's main Reconcile calls createOrUpdateArgoCD(...) (internal/controller/argo.go) unconditionally on every reconcile pass:
// We only update the clusterwide argo instance so we can define our own 'initcontainers' section
err = createOrUpdateArgoCD(r.dynamicClient, r.fullClient, getClusterWideArgoName(), clusterWideNS, patternsOperatorConfig)createOrUpdateArgoCD (argo.go) always builds a fresh hardcoded desired spec via newArgoCD(...) and, when the object already exists, calls a plain client.Resource(gvr).Namespace(namespace).Update(...) on it -- with no diff check against the live object beforehand:
} else { // update it
oldArgo, oldUnstructured, errGet := getArgoCDFunc(client, name, namespace)
...
argo.SetResourceVersion(oldArgo.GetResourceVersion())
...
_, err = client.Resource(gvr).Namespace(namespace).Update(context.TODO(), newArgo, metav1.UpdateOptions{})
}Since the operator appears to also react to changes on the ArgoCD object it just wrote (directly or transitively, e.g. via the Application/Pattern watch chain), this Update -> triggers another reconcile -> Update again, in a self-sustaining loop that runs far faster than the documented ReconcileLoopRequeueTime (180s) steady-state interval.
Impact (observed live on OpenShift, patterns-operator v0.0.77, gitops-operator v1.21.0)
oc get argocd <vpArgoNamespace> -n <vpArgoNamespace> -o jsonpath='{.metadata.resourceVersion}'polled every ~10-15s shows the resourceVersion incrementing continuously with no other actor touching the object (confirmed via--show-managed-fields: the sole non-status managedFields entry forspecbelongs to field-managermanager,operation: Update, matching controller-runtime's default field-manager name for a plain typed/dynamic client Update -- not ServerSideApply).- Confirmed the root cause is this operator specifically (not ACM policies, not the
clustergroupchart, not any local pattern chart) by scalingpatterns-operator-controller-managerto 0 replicas: theArgoCDCR'sresourceVersionimmediately stopped changing and stayed stable for 90+ seconds; scaling back to 1 replica, the churn resumed within the next reconcile. openshift-gitops-operator's own controller reacts to each of these Update()s by recomputing/reapplying the Deployments/StatefulSet it manages ("Updating StatefulSet ... updating volumes/container resources/container command/container env", "Updating Deployment 'vp-gitops-redis/repo-server/server/applicationset-controller' - updating volumes/...") roughly every 15-30s, which restarts the<name>-application-controllerStatefulSet pod on that same cadence.- Practical effect: Argo CD
Applicationsync operations that areRunningwhen the controller pod restarts get interrupted and can remain stuck inoperationState.phase: Running(observed for over an hour on one Application in our environment) instead of completing, andstatus.sync.statusnever settles toSyncedfor several Applications even though the git repo has no outstanding diff. - Separately (same root cause, different symptom):
spec.rbacset bynewArgoCD()'s hardcoded baseline (defaultPolicy: role:readonly, a 3-line policy granting onlysystem:cluster-admins/cluster-admins/admin->role:admin,scopes: [groups,email]) always wins over anything a pattern's own chart tries to set on the same CR (even viaServerSideApply=true), since this operator does a plain Update every reconcile. There is no values/Pattern-CR override hook for this baseline today (unlikeclustergroup-chart'sclusterGroup.argoCD.rbac, added in v0.9.50, which is a no-op forsingleArgoCDsinceclustergroup-chart's owntemplates/plumbing/argocd.yamlrenders nothing at all whenglobal.singleArgoCD=true).
Suggested fix
Before calling Update() in createOrUpdateArgoCD, compute a semantic diff between oldArgo.Spec (or the relevant subset this function owns) and the freshly-built argo.Spec, and skip the API call entirely when they already match. This alone should stop the self-sustaining loop, since there would be nothing left to re-trigger the watch after the first successful convergence.
As a secondary/independent improvement, consider exposing an override hook (mirroring clusterGroup.argoCD.rbac on the non-singleArgoCD path) for at least spec.rbac on the clusterwide instance, so patterns that need a local ArgoCD user with more than read-only access (e.g. an MCP-server-style integration) aren't stuck with the hardcoded 3-line baseline policy.
Environment
patterns-operator.v0.0.77(catalog channelfast, currently the latest available)openshift-gitops-operator.v1.21.0global.singleArgoCD: true, single-cluster hub-only reproduction (also affects hub+spoke topologies)- OpenShift 4.20
Activity
Definitive root cause for ArgoCD singleton churn: patterns-operator i…
mbaldessari commentedon Jul 5, 2026
Hi and thanks for the report!
A couple of things:
If you tweak the RBACs externally (via server-side apply or other mechanisms), then yes, there will be two things racing against each other and the RV will constantly increase.
Having said the above, I think we can definitely improve things here:
gitops.additionalArgoAdminsstring in thepattern-operator-configmapis not sufficient, we should also be able to expose the full argo RBAC override to the user, I'll tackle thatmbaldessari commentedon Jul 5, 2026
#754 will expose the
gitops.argoRBACkey in the configmap (the corresponding validatedpatterns/pattern-install-chart#46 will allow to set it directly in a pattern like this https://github.com/mbaldessari/multicloud-gitops/blob/fix-single-argoreconcile/values-global.yaml#L9-L17)maximilianoPizarro commentedon Jul 6, 2026
Thanks Michele for the detailed explanation and the PRs — confirmed on our end with testing:
global.singleArgoCDis indeed a clustergroup-chart concept, not patterns-operatorclusterGroup.argoCD.rbacoverrides drifting the singleton ArgoCD CR spec on every reconcile (exactly what you described)gitops.argoRBACkey exposed via Add gitops.argoRBAC config key for full ArgoCD RBAC override #754 + pattern-install-chart#46, we can set RBAC policy correctly at the right layer without triggering the loopAppreciate the time and the fix. Closing this as resolved.
mbaldessari commentedon Jul 6, 2026
FWIW we just released 0.0.78 which exposes the rbac knob (https://github.com/validatedpatterns/pattern-install-chart/blob/main/values.yaml#L32 is the value for the CM) and we also made the ArgoCD obj updating less onerous. Thanks again for bringing this to our attention