From d0b2a6cfd7b28a32869327343c304992ae052ed8 Mon Sep 17 00:00:00 2001 From: "argo-cd-cherry-pick-bot[bot]" <231928284+argo-cd-cherry-pick-bot[bot]@users.noreply.github.com> Date: Wed, 18 Feb 2026 13:31:39 +0200 Subject: [PATCH] fix: Fix excessive ls-remote requests on monorepos with Auto Sync enabled apps (26277) (cherry-pick #26278 for 3.3) (#26372) Signed-off-by: Eugene Doudine Co-authored-by: dudinea Co-authored-by: Dan Garfield Co-authored-by: Regina Voloshin --- controller/appcontroller.go | 14 +++++++++++-- controller/appcontroller_test.go | 35 ++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/controller/appcontroller.go b/controller/appcontroller.go index b0cdaa5fe3..5bd7efd6ea 100644 --- a/controller/appcontroller.go +++ b/controller/appcontroller.go @@ -1559,8 +1559,18 @@ func (ctrl *ApplicationController) processRequestedAppOperation(app *appv1.Appli // if we just completed an operation, force a refresh so that UI will report up-to-date // sync/health information if _, err := cache.MetaNamespaceKeyFunc(app); err == nil { - // force app refresh with using CompareWithLatest comparison type and trigger app reconciliation loop - ctrl.requestAppRefresh(app.QualifiedName(), CompareWithLatestForceResolve.Pointer(), nil) + var compareWith CompareWith + if state.Operation.InitiatedBy.Automated { + // Do not force revision resolution on automated operations because + // this would cause excessive Ls-Remote requests on monorepo commits + compareWith = CompareWithLatest + } else { + // Force app refresh with using most recent resolved revision after sync, + // so UI won't show a just synced application being out of sync if it was + // synced after commit but before app. refresh (see #18153) + compareWith = CompareWithLatestForceResolve + } + ctrl.requestAppRefresh(app.QualifiedName(), compareWith.Pointer(), nil) } else { logCtx.WithError(err).Warn("Fails to requeue application") } diff --git a/controller/appcontroller_test.go b/controller/appcontroller_test.go index 52c17aa351..8476751757 100644 --- a/controller/appcontroller_test.go +++ b/controller/appcontroller_test.go @@ -2633,6 +2633,41 @@ func TestProcessRequestedAppOperation_Successful(t *testing.T) { assert.Equal(t, CompareWithLatestForceResolve, level) } +func TestProcessRequestedAppAutomatedOperation_Successful(t *testing.T) { + app := newFakeApp() + app.Spec.Project = "default" + app.Operation = &v1alpha1.Operation{ + Sync: &v1alpha1.SyncOperation{}, + InitiatedBy: v1alpha1.OperationInitiator{ + Automated: true, + }, + } + ctrl := newFakeController(t.Context(), &fakeData{ + apps: []runtime.Object{app, &defaultProj}, + manifestResponses: []*apiclient.ManifestResponse{{ + Manifests: []string{}, + }}, + }, nil) + fakeAppCs := ctrl.applicationClientset.(*appclientset.Clientset) + receivedPatch := map[string]any{} + fakeAppCs.PrependReactor("patch", "*", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) { + if patchAction, ok := action.(kubetesting.PatchAction); ok { + require.NoError(t, json.Unmarshal(patchAction.GetPatch(), &receivedPatch)) + } + return true, &v1alpha1.Application{}, nil + }) + + ctrl.processRequestedAppOperation(app) + + phase, _, _ := unstructured.NestedString(receivedPatch, "status", "operationState", "phase") + message, _, _ := unstructured.NestedString(receivedPatch, "status", "operationState", "message") + assert.Equal(t, string(synccommon.OperationSucceeded), phase) + assert.Equal(t, "successfully synced (no more tasks)", message) + ok, level := ctrl.isRefreshRequested(ctrl.toAppKey(app.Name)) + assert.True(t, ok) + assert.Equal(t, CompareWithLatest, level) +} + func TestProcessRequestedAppOperation_SyncTimeout(t *testing.T) { testCases := []struct { name string