- 
                Notifications
    You must be signed in to change notification settings 
- Fork 6.5k
Description
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
After upgrading to ArgoCD 3.x (where --persist-resource-health=false by default), the Application.Status.Resources[].Health field is not populated when calling the API GetApplication with refresh=true, even though:
- Application.Status.ResourceHealthSourceis set to- "appTree"
- The ArgoCD UI displays health statuses correctly
- The same API call without refresh=truereturns health statuses correctly
- The health data exists in Redis cache
To Reproduce
- Deploy ArgoCD 3.x with default settings (where --persist-resource-health=false)
- Create an application with multiple resources
- Call the ArgoCD API: GetApplicationwith parameterrefresh=truecurl -H "Authorization: Bearer $TOKEN" \ "https://argocd-server/api/v1/applications/my-app?refresh=true" 
- Observe that status.resources[].healthisnullfor all resources
- Call the same API without refresh=true:curl -H "Authorization: Bearer $TOKEN" \ "https://argocd-server/api/v1/applications/my-app" 
- Observe that status.resources[].healthis properly populated
Expected behavior
When calling GetApplication with refresh=true, the Application.Status.Resources[].Health field should be populated with health statuses from the ApplicationTree cache (Redis), just like when calling without refresh.
The health statuses should be consistent regardless of whether refresh parameter is used or not.
Root Cause
The issue is in the Get() method in server/application/application.go:
- Line 777: inferResourcesStatusHealth(a)is called for the initial application object
- Lines 796-862: When refresh != nil, the code waits for an updated application from the broadcaster
- Line 858: The updated application is returned via event.Application.DeepCopy()without callinginferResourcesStatusHealth()again
This means the refreshed application object never gets its health statuses populated from the cache.
Code Reference
The fix should be to call s.inferResourcesStatusHealth() on the application object before returning it on line 858:
case event := <-events:
    if appVersion, err := strconv.Atoi(event.Application.ResourceVersion); err == nil && appVersion > minVersion {
        annotations := event.Application.GetAnnotations()
        if annotations == nil {
            annotations = make(map[string]string)
        }
        if _, ok := annotations[v1alpha1.AnnotationKeyRefresh]; !ok {
            refreshedApp := event.Application.DeepCopy()
            s.inferResourcesStatusHealth(refreshedApp)  // added this line
            return refreshedApp, nil
        }
    }Version
argocd-server: v3.1.7+511ebd7
  BuildDate: 2025-09-22T22:21:06Z
  GitCommit: 511ebd799e64f9af6484feaa84a5e718a77620d5
  GitTreeState: clean
  GoVersion: go1.24.6
  Compiler: gc
  Platform: linux/amd64
  Kustomize Version: v5.7.0 2025-06-28T07:00:07Z
  Helm Version: v3.18.4+gd80839c
  Kubectl Version: v0.33.1
  Jsonnet Version: v0.21.0Additional Context
- Related to issue: Persist resource health in Redis instead of the Application CR by default #10312
- The default value of --persist-resource-healthchanged tofalsein v3.0 for optimization
- When persist-resource-health=false, health statuses should come from Redis (ApplicationTree) instead of CR
- The inferResourcesStatusHealth()function (line 2776) correctly retrieves health from cache whenResourceHealthSource == ResourceHealthLocationAppTree
- This bug affects any client that uses refresh=trueparameter (like backend services monitoring application health)