Skip to content

Show Argo's own per-resource verdicts from argocd-server on Argo CD 3, with or without a token - #1751

Merged
nadaverell merged 11 commits into
mainfrom
feat/argo-api-resource-health
Sep 14, 2026
Merged

nadaverell merged 11 commits into
mainfrom
feat/argo-api-resource-health

Conversation

@nadaverell

@nadaverell nadaverell commented Sep 13, 2026 •

Copy link
Copy Markdown
Contributor

Fast-follow to #1743. On Argo CD 3 the Application object no longer carries per-resource health, so Radar showed its own read of each resource, labelled as Radar's. argocd-server still has Argo's verdicts in its own cache. When Radar can reach it — with the integration connected, or with no setup at all on installs that serve reads anonymously — it now shows those instead, and the page looks exactly like it does on Argo CD 2.x: Argo's per-resource health, no notice, no "Found by Radar" markers. When it can't, the page says why and how to get there.

What changes

Argo's verdicts from argocd-server. For an Application in appTree mode that deploys to this cluster, Radar asks the Argo CD API server for the Application (GET /api/v1/applications/{name}, no refresh) to establish identity and mode, then reads per-resource health from its resource-tree. The tree is the source rather than the GET's own inlined health because Argo CD 3.0's GET-side inference keys the tree cache by bare app name, which can hand an Application outside Argo's namespace a same-named app's verdicts; the tree is scoped correctly on every 3.x. The answer is accepted only when its UID matches the local Application, so a different Argo install serving a same-named app is ignored.

Argo's answer is the whole answer. When it arrives, declared nodes take Argo's health with healthSource: controllerApi, Radar's topology fill is cleared for kinds Argo has no check for, the issues-engine overlay doesn't run, and the notice doesn't show. resourceHealthFromApi on the insights summary and healthFromApi on the tree tell consumers so.

With a token, or without one. With the Argo CD integration connected (Settings → Argo CD), the existing client and token are used. With nothing configured, this read path runs the existing argocd-server discovery and asks without credentials. A client that carries no token skips TLS verification, so the probe and the credential-free read reach the default in-cluster argocd-server with its self-signed certificate; every client that sends the token still honours argoCdInsecureTls, and a certificate the token-bearing client can't verify is reported with that setting named. Radar never sends credentials it wasn't given; refusals are cached per app for 15s, and discovery or a failed connect is throttled to once per retry interval, so a polling detail page never hammers an install that says no.

A tokenless connection counts only once it answers. A reachable argocd-server that refuses reads is not an integration: it stays "Not connected" in Settings and never lights the diff. Once an anonymous read has succeeded (users.anonymous.enabled with a default role that can read applications), that connection is the integration — Settings shows "Connected · no token needed" with a line explaining when a token or URL still matters, and the Git-rendered diff and revision metadata work over it too.

A configured connection that doesn't deliver says so. With the integration set up and no usable answer — token rejected, requires a token, server unreachable, request timed out, a different install answering, unknown app, or anything else — the insights summary carries the reason (resourceHealthApiError) and the notice leads with it: "Radar couldn't read Argo's per-resource health from your Argo CD server: the server couldn't be reached. Check Argo CD in Settings." The Argo CD status carries the same failure in plain words, so the Settings section the notice links to shows what's wrong.

The way out, from the page. When Radar found nothing to mark on a Degraded app, the notice offers connecting Radar to argocd-server first and the persist knob second. The Settings clause is host-injected through the layout's onOpenSettings: standalone Radar renders it as a link that opens the Argo CD section (and focuses the token field when nothing is set up); an embedded host without that page gets only the Argo-side knob. Connecting in Settings refreshes the GitOps queries, so the page the user came from shows Argo's verdicts when they get back. The Argo CD Settings section names per-resource health alongside the diff.

Docs. docs/gitops.md describes the three health sources and the connection semantics; radar-docs counterpart is skyhook-dev/radar-docs#95, to merge together.

Testing

  • go test in both modules (client; manager: cache, negative cache, throttle for unconfigured and configured failures, anonymous-connection gating, TLS modes, probe-failure reporting; overlay: identity, mode, remote, clearing Radar fill, failure carrying, reason phrasing; status endpoint), make tsc, k8s-ui vitest (3684).
  • Live on a kind cluster with Argo CD v3.5.2:
    • nothing configured, anonymous disabled → 401 → Radar's read with markers, Settings "Not connected", no managed-resources calls; first open 0.22s from a laptop (port-forward + probe), repeats ~1ms.
    • nothing configured, users.anonymous.enabled + policy.default: role:readonly → Argo's verdicts with controllerApi (Widget Degraded via its Lua check with Argo's message, Deployment Progressing, Gadget no verdict), no notice, Settings connected + anonymous, diff capability lit.
    • integration connected with a token → same verdicts, no notice, no markers.
    • configured URL unreachable → notice with "the server couldn't be reached" and the Settings link landing on the Argo CD section; first open 0.05s, repeats throttled.
  • Anonymous negative case against a real Argo CD 3.2.6 install with defaults: 401, not connected.
  • Plain-GET vs resource-tree behaviour verified from Argo CD source (v3.0.0 vs v3.2.6 inferResourcesStatusHealth).
  • Visual: notice (error and findings states) and the Argo CD Settings section rendered and checked on a dev build.

Notes

  • The anonymous connection is discovered by the Argo CD 3 health read, so on an Argo CD 2.x install (inline health) nothing triggers it and the diff still needs a token there — by design for now; making the capabilities probe try anonymously would open a port-forward on every app open for every unconfigured user.

Note

Medium Risk
Adds outbound calls to argocd-server on GitOps detail (including unconfigured anonymous discovery) and changes when the integration counts as connected; core auth paths are extended but bounded by caching, UID checks, and existing probe guards.

Overview
On Argo CD 3 (appTree mode), Radar can now show Argo’s per-resource health from argocd-server instead of defaulting to Radar’s own cluster read. The GitOps detail path calls ApplicationHealthCached: GET the Application for UID/mode, then resource-tree when health isn’t persisted on the CR (tree is authoritative in appTree; mis-scoped GET inference is not used as fallback). Successful answers overlay the tree with controllerApi health, skip the Radar issues overlay, and hide the “Found by Radar” notice.

The Argo CD manager also supports credential-free reads when nothing is configured: discover argocd-server, probe/read without a token (tokenless clients skip TLS verify for self-signed in-cluster certs). A reachable server that 401s is not “connected”; after a successful anonymous read, Settings shows “Connected · no token needed” and diff/revision metadata work like a normal integration. Health fetches are 15s cached (including failures), with throttled retries so 2s polling doesn’t hammer refusals or failed discovery.

When integration is configured but health can’t be fetched, the UI surfaces resourceHealthApiError and a notice with Settings remediation; anonymous/unconfigured failures fall through to Radar’s read quietly. Settings gains probe failure reasons, anonymous status, token focus on connect, and GitOps query invalidation after saving Argo CD config.

Reviewed by Cursor Bugbot for commit 94f71ca. Bugbot is set up for automated code reviews on this repo. Configure here.

…r on Argo CD 3

When the Application doesn't carry per-resource health (appTree mode) and
argocd-server can be reached — the integration connected, or an install
that allows anonymous read — Radar asks the server for Argo's verdicts and
shows those instead of its own read: a plain GET first, resource-tree when
the GET answers without health. The answer must be about the same
Application (UID) and the app must deploy to this cluster. Argo's verdicts
are authoritative for the whole app: Radar's topology fill is cleared for
kinds Argo has no check for, the issues-engine overlay doesn't run, and the
notice doesn't show. Refusals are cached and unconfigured discovery is
throttled so a polling page never hammers an install that says no.
…a superseded answer

Argo CD 3.0's GET-side health inference keys the tree cache by bare app
name, so an Application outside Argo's namespace could be handed a
same-named app's verdicts. The plain GET now only establishes identity and
mode; in appTree mode the health comes from resource-tree, which every 3.x
scopes correctly. The cache write checks the manager generation under the
lock so a reset mid-fetch discards the answer instead of caching it under
the new connection.
@nadaverell
nadaverell requested a review from hisco as a code owner September 13, 2026 22:57
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Use argocd-server health verdicts for Argo CD 3 resource trees

✨ Enhancement 🐞 Bug fix 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Fetches authoritative per-resource health from argocd-server for local Argo CD 3 applications.
• Caches API results and throttles anonymous discovery while preserving Radar fallback behavior.
• Propagates health provenance to suppress Radar markers and notices when Argo responds.
Diagram

sequenceDiagram
    actor User
    participant UI as GitOps Detail
    participant Resolver as GitOps Resolver
    participant Manager as Argo Manager
    participant Argo as argocd-server
    participant Radar as Radar Issues
    User->>UI: Open application
    UI->>Resolver: Request resource tree
    alt Local appTree application
        Resolver->>Manager: Request cached health
        Manager->>Argo: GET application identity
        Argo-->>Manager: UID and mode
        Manager->>Argo: GET resource-tree
        Argo-->>Manager: Resource verdicts
        Manager-->>Resolver: Cached API health
    else API unavailable or ineligible
        Resolver->>Radar: Derive local findings
        Radar-->>Resolver: Radar health
    end
    Resolver-->>UI: Tree with provenance
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Require persisted Application health
  • ➕ Avoids additional argocd-server requests and caches.
  • ➕ Keeps health entirely within the Kubernetes Application object.
  • ➖ Requires every operator to change Argo CD configuration and restart controllers.
  • ➖ Does not restore the default Argo CD 3 experience without administrative setup.
2. Use Application GET inference only
  • ➕ Requires one API request per refresh.
  • ➕ Still provides Application UID and mode for validation.
  • ➖ Argo CD 3.0 may infer health from a bare-name cache key.
  • ➖ Can return verdicts for a same-named Application in another namespace.
3. Query resource-tree directly
  • ➕ Reads the correctly namespace-scoped tree with fewer successful-path requests.
  • ➕ Avoids potentially mis-scoped GET-side inferred health.
  • ➖ The resource-tree response lacks the Application UID needed for identity validation.
  • ➖ Cannot independently establish the Application's health-source mode.

Recommendation: The PR's two-step approach is the strongest option: use the Application GET for UID and mode, then use the namespace-scoped resource-tree as the appTree health source. Retain the bounded positive/negative cache and discovery throttle to make polling safe, while ensuring successful resource-tree responses—including intentionally healthless trees—have consistent authority semantics throughout the overlay path.

Files changed (15) +577 / -8

Enhancement (9) +353 / -6
manager.goCache and coordinate Application health API reads +117/-0

Cache and coordinate Application health API reads

• Adds per-Application health caching, negative caching, anonymous discovery throttling, and connection-generation validation. Fetches Application identity and mode before selecting resource-tree health for appTree applications.

internal/argocd/manager.go

gitops_handlers.goOverlay Argo API health before Radar findings +77/-1

Overlay Argo API health before Radar findings

• Adds the server-side API health overlay for local appTree applications with UID validation and a five-second timeout. Applied Argo verdicts receive controller API provenance, while unavailable or rejected responses retain Radar fallback behavior.

internal/server/gitops_handlers.go

GitOpsHealthSourceNotice.tsxSuppress Radar notices for API-sourced Argo health +5/-4

Suppress Radar notices for API-sourced Argo health

• Extends notice selection with API provenance so appTree applications using Argo verdicts remain silent. The no-findings message now directs users to the Argo CD integration settings before suggesting controller persistence.

packages/k8s-ui/src/components/gitops/GitOpsHealthSourceNotice.tsx

gitops-insights.tsExpose API health provenance in insight summaries +3/-0

Expose API health provenance in insight summaries

• Adds the optional resourceHealthFromApi flag to the shared GitOps insight summary contract.

packages/k8s-ui/src/types/gitops-insights.ts

gitops-tree.tsModel controller API health in UI tree types +7/-1

Model controller API health in UI tree types

• Adds controllerApi as a health source and healthFromApi as tree-level provenance. This distinguishes API-delivered Argo verdicts from Radar-derived findings.

packages/k8s-ui/src/types/gitops-tree.ts

client.goAdd Application and resource-tree health clients +83/-0

Add Application and resource-tree health clients

• Adds typed calls for the Application and resource-tree endpoints, including namespace and project scoping. Parses resource identity, health status, messages, Application UID, and health-source mode.

pkg/argoapi/client.go

types.goDefine Application health API models +47/-0

Define Application health API models

• Introduces query, per-resource health, and Application health types used across the client, manager, and overlay. Adds a helper for detecting populated Argo health verdicts.

pkg/argoapi/types.go

insights.goPropagate API provenance into GitOps insights +4/-0

Propagate API provenance into GitOps insights

• Adds resourceHealthFromApi to insight summaries and derives it from the resolved resource tree.

pkg/gitops/insights/insights.go

types.goAdd controller API provenance to resource trees +10/-0

Add controller API provenance to resource trees

• Defines the controllerApi health source and tree-level healthFromApi marker. These fields allow downstream consumers to treat API-fetched verdicts as authoritative Argo health.

pkg/gitops/tree/types.go

Refactor (1) +4 / -0
graph.goExpose health-to-topology conversion +4/-0

Expose health-to-topology conversion

• Exports the existing controller-health mapping so API overlays can assign consistent topology states.

pkg/gitops/tree/graph.go

Tests (4) +217 / -0
manager_test.goTest health caching, tree selection, and throttling +97/-0

Test health caching, tree selection, and throttling

• Covers inline health, authoritative appTree responses, healthless trees, cached authorization failures, and throttled unconfigured discovery.

internal/argocd/manager_test.go

gitops_health_overlay_test.goTest Argo API health overlay eligibility and provenance +70/-0

Test Argo API health overlay eligibility and provenance

• Verifies verdict application, controller API provenance, topology conversion, Radar-fill clearing, and generated-node preservation. Also covers inline mode, remote destinations, missing health, and UID mismatch rejection.

internal/server/gitops_health_overlay_test.go

health-provenance.test.tsTest controller API provenance and notice suppression +3/-0

Test controller API provenance and notice suppression

• Confirms controller API health does not receive Radar attribution, validates the revised settings copy, and ensures API-sourced appTree health suppresses the notice.

packages/k8s-ui/src/components/gitops/health-provenance.test.ts

client_test.goTest Application health API parsing and requests +47/-0

Test Application health API parsing and requests

• Validates both endpoint paths, query parameters, UID and mode parsing, health messages, tree nodes, and missing-name validation.

pkg/argoapi/client_test.go

Documentation (1) +3 / -2
gitops.mdDocument argocd-server as an Argo health source +3/-2

Document argocd-server as an Argo health source

• Explains how Radar obtains Argo CD 3 per-resource verdicts from the resource-tree API. Documents authenticated integration, anonymous access, identity restrictions, fallback behavior, and setup options.

docs/gitops.md

@qodo-code-review

qodo-code-review Bot commented Sep 13, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Removed verdicts remain on tree nodes ✓ Resolved 🐞 Bug ≡ Correctness
Description
overlayArgoAPIHealth clears a declared node missing from Argo’s answer only when its existing
source is Radar, leaving controller-sourced health untouched. If an appTree Application still
carries an older per-resource value that the current resource tree no longer reports, users continue
to see that stale verdict even though the API answer is marked authoritative.
Code

internal/server/gitops_handlers.go[R240-242]

+			if n.HealthSource == gitopstree.HealthSourceRadar {
+				n.Health, n.HealthSource, n.HealthReason, n.HealthMessage, n.HealthSeverity = "", "", "", "", ""
+				n.TopologyStatus = "unknown"
Relevance

●●● Strong

Authoritative whole-answer overlays should clear stale values regardless of their previous
provenance.

PR-#1146

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The overlay claims Argo’s API response is the whole answer, yet its clearing branch is restricted to
HealthSourceRadar. The existing appTree overlay fixture includes a declared controller-sourced
node, proving this state is represented by the current tree model, but the new test does not verify
that it is cleared.

internal/server/gitops_handlers.go[198-201]
internal/server/gitops_health_overlay_test.go[23-35]
internal/server/gitops_health_overlay_test.go[109-134]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
An authoritative API answer only clears Radar-sourced values, so older controller-sourced health can survive after Argo stops returning a verdict for that resource.

## Fix Focus Areas
- internal/server/gitops_handlers.go[224-253]
- internal/server/gitops_health_overlay_test.go[109-134]

## Recommended Fix
For every declared node without a non-empty matching API verdict, clear all health, provenance, reason, message, severity, and derived topology fields regardless of the previous health source. Add coverage for an appTree node that begins with controller-sourced health but is absent or healthless in the API answer.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Tree errors show another app’s health ✓ Resolved 🐞 Bug ≡ Correctness
Description
fetchApplicationHealth returns the plain Application GET response when the resource-tree request
fails after detecting ResourceHealthSource == "appTree", even though GET-side inference may be
keyed by bare application name. For a transient tree failure involving same-named applications
outside Argo’s namespace, incorrectly inferred resource rows reach the local-tree overlay, and the
correct UID from the plain GET makes the later identity check pass.
Code

internal/argocd/manager.go[R1023-1024]

+	}
+	return app, gen, nil
Relevance

●●● Strong

The PR explicitly treats GET inference as unsafe; falling back after tree failure reintroduces the
documented identity-scoping bug.

PR-#1146

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The manager recognizes appTree mode and identifies GET-side inference as unsafe for same-named
applications, but it returns the tree only on success and otherwise falls back to app. The overlay
then accepts the response using the correctly fetched Application UID and maps its potentially
mis-scoped resource health rows onto the local tree, so UID validation cannot distinguish the mixed
response.

internal/argocd/manager.go[936-943]
pkg/argoapi/client.go[144-147]
internal/server/gitops_handlers.go[216-250]
internal/argocd/manager.go[1014-1024]
internal/server/gitops_handlers.go[220-250]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
In `appTree` mode, a `resource-tree` failure falls back to inline GET-inferred health even though that inference may be incorrectly scoped for same-named applications. This uses precisely the potentially mis-scoped source that the `appTree` path was introduced to avoid.

## Fix Focus Areas
- internal/argocd/manager.go[993-1025]
- pkg/argoapi/client.go[144-185]

## Recommended Fix
When `ResourceHealthSource` is `appTree`, return the `ResourceTree` error if the tree request fails instead of returning `app`, allowing the caller to use Radar's safe fallback. Reserve inline GET health for applications whose reported mode is not `appTree`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Hub users get Radar verdicts ✓ Resolved 🔗 Cross-repo conflict ≡ Correctness
Description
overlayArgoAPIHealth uses !health.HasHealth() to reject a successfully fetched, authoritative
resource tree when none of its resources has a non-empty health verdict, before clearing
Radar-derived values or setting HealthFromAPI. When Argo has no checks for any declared resource,
resolveGitOpsTree reaches overlayRadarHealth, and Radar Hub Web renders those Radar findings
through its shared GitOpsDetailLayout.
Code

internal/server/gitops_handlers.go[R217-218]

+	if health == nil || !health.HasHealth() {
+		return false
Relevance

●●● Strong

The stated whole-answer semantics require accepting healthless authoritative trees; rejecting them
incorrectly re-enables Radar findings.

PR-#1146

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
HasHealth returns false unless at least one resource has a non-empty verdict, even though the
manager deliberately preserves a successfully fetched healthless resource tree as Argo’s complete
answer rather than falling back to GET health. The server rejects that result before setting API
provenance or applying the overlay’s whole-answer behavior, so the changed request flow invokes
overlayRadarHealth; Radar Hub Web then passes the resulting insight through the shared Radar
packages directly into GitOpsDetailLayout, where the fallback findings are rendered.

internal/argocd/manager.go[1014-1024]
internal/server/gitops_handlers.go[214-253]
internal/server/gitops_handlers.go[275-283]
internal/server/gitops_handlers.go[178-180]
internal/server/gitops_handlers.go[216-253]
pkg/argoapi/types.go[127-139]
internal/argocd/manager.go[1014-1021]
internal/server/gitops_handlers.go[175-180]
External repo: skyhook-dev/radar-hub-web, package.json [16-20]
External repo: skyhook-dev/radar-hub-web, src/pages/fleet/GitOpsDetailPage.tsx [463-478]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A successful Argo `resource-tree` response is authoritative even when none of its resources has a health value. The current `HasHealth` guard treats that response as absent, activates Radar’s fallback overlay, and exposes Radar-derived verdicts to embedded Radar Hub Web users.

## Fix Focus Areas
- internal/server/gitops_handlers.go[207-253]
- internal/server/gitops_health_overlay_test.go[136-165]
- internal/argocd/manager.go[1014-1024]
- pkg/argoapi/types.go[115-140]

## Recommended Fix
Represent a successful resource-tree answer independently of `HasHealth()`. Reject only a nil or otherwise invalid API result; after validating the UID, accept a non-nil healthless response as authoritative, clear existing Radar-derived health from declared topology nodes where Argo provides no verdict, set `HealthFromAPI`, and return true. Update the healthless-response test to require this behavior.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View high (1)
4. Resource issues use stale verdicts ✓ Resolved 🐞 Bug ≡ Correctness
Description
The API overlay updates tree nodes, but argoResourceChanges continues to prefer any non-empty
health embedded in the Application object over the overlaid tree value. When an appTree object
retains a stale verdict and the API returns a newer one, the tree summary shows the API result while
resource changes and generated Issues use the stale value.
Code

internal/server/gitops_handlers.go[R246-249]

+		n.Health = rh.Health
+		n.HealthSource = gitopstree.HealthSourceControllerAPI
+		n.HealthReason, n.HealthSeverity = "", ""
+		n.HealthMessage = rh.Message
Relevance

●●● Strong

Downstream issue generation must consume the authoritative overlaid value, not stale object fields.

PR-#1146

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The changed overlay and summary use the API-updated tree, but argoResourceChanges reads object
health first and only checks tree health when that value is empty. buildIssues consumes those
changes, so contradictory object health still controls resource issue generation.

internal/server/gitops_handlers.go[231-253]
pkg/gitops/insights/insights.go[1155-1192]
pkg/gitops/insights/insights.go[605-635]
pkg/gitops/insights/insights.go[380-387]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Authoritative API health is written only to tree nodes, while the Insights changes and issue paths continue prioritizing stale health from the Application object.

## Fix Focus Areas
- internal/server/gitops_handlers.go[231-253]
- pkg/gitops/insights/insights.go[609-635]
- pkg/gitops/insights/insights.go[1155-1192]

## Recommended Fix
When `resourceTree.HealthFromAPI` is true, make `argoResourceChanges` use the matching tree node as the authoritative health source before consulting Application status. Preserve explicit empty API verdicts as well, so stale object health is cleared rather than restored, and add tests covering conflicting API and object verdicts.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

5. New credentials inherit a stale connection error ✓ Resolved 🐞 Bug ☼ Reliability
Description
On an error from connectedClient, fetchApplicationHealth reads m.generation only after the
failed probe has returned, rather than retaining the generation of the client/probe that failed. If
SetConfig or Reset races that probe, the outer generation comparison accepts the old error as
belonging to the new connection and negative-caches it for 15 seconds.
Code

internal/argocd/manager.go[R996-1000]

+	if _, err := m.connectedClient(ctx); err != nil {
+		m.mu.Lock()
+		gen := m.generation
+		m.mu.Unlock()
+		return nil, gen, err
Relevance

●●● Strong

Accepted cache precedents prioritize generation/context validation to prevent superseded results
being cached.

PR-#1313
PR-#1736

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Probe detects a configuration change and returns errStaleProbe after comparing its saved
snapshot generation with the live manager generation. The new fetch code then reads the now-current
generation on that error path, causing its subsequent equality check to pass and its cache write to
store the superseded failure.

internal/argocd/manager.go[784-797]
internal/argocd/manager.go[995-1000]
internal/argocd/manager.go[973-990]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

Issue description
A failed connection attempt can be stamped with a configuration generation installed after the attempt began. This permits the old failure to enter the new configuration's per-application negative cache.
Fix Focus Areas
- internal/argocd/manager.go[966-990]
- internal/argocd/manager.go[995-1009]
Recommended Fix
Capture the manager generation before invoking `connectedClient`, or return the probe snapshot generation alongside errors, and reject all results whose attempted generation differs from the current one before cache insertion.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Consumers receive nonstandard health ✓ Resolved 📘 Rule violation ≡ Correctness
Description
overlayArgoAPIHealth assigns the external rh.Health string directly to n.Health, while only
TopologyStatus passes through HealthToTopology. Any Argo value such as Progressing,
Suspended, or Missing therefore remains stored and exposed outside the canonical tone
vocabulary.
Code

internal/server/gitops_handlers.go[R246-250]

+		n.Health = rh.Health
+		n.HealthSource = gitopstree.HealthSourceControllerAPI
+		n.HealthReason, n.HealthSeverity = "", ""
+		n.HealthMessage = rh.Message
+		n.TopologyStatus = gitopstree.HealthToTopology(rh.Health)
Relevance

●●● Strong

The repository accepts findings requiring external health values to use canonical internal mappings
before storage.

PR-#1146

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 3036741 requires external health strings to pass through a central mapper before
they are stored or exposed. The overlay stores rh.Health directly in n.Health; the adjacent
mapping only normalizes the separate topology field, and HealthToTopology confirms that Argo's
provider-specific values are mapped to canonical tones elsewhere.

Rule 3036741: Use only the standard HealthLevel tones and normalize external values
internal/server/gitops_handlers.go[246-250]
pkg/gitops/tree/graph.go[230-243]
pkg/argoapi/client.go[178-183]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Raw health values received from the Argo CD API are assigned to the resource tree and exposed to consumers without passing through the canonical health-tone mapper.

## Fix Focus Areas
- internal/server/gitops_handlers.go[246-250]
- pkg/gitops/tree/graph.go[230-243]

## Recommended Fix
Normalize the external health value through the central health-to-tone mapping before storing or exposing it. If the original Argo verdict must remain available for display, preserve it in a separately named raw or provider-specific field while keeping canonical health fields restricted to standard tones.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Responses without identity are trusted ✓ Resolved 🐞 Bug ⛨ Security
Description
overlayArgoAPIHealth rejects mismatches only when both the local and returned UIDs are non-empty,
so a health response lacking its UID passes validation. If a malformed or incorrectly scoped Argo
endpoint returns a same-named application without metadata.uid, its resource verdicts reach the
local application tree despite identity never being established.
Code

internal/server/gitops_handlers.go[220]

+	if uid := string(root.GetUID()); uid != "" && health.UID != "" && health.UID != uid {
Relevance

●●● Strong

Identity validation is security-sensitive, and accepted precedents consistently require explicit
context/resource identity boundaries.

PR-#1313

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The API client leaves ApplicationHealth.UID empty when metadata.uid is absent, while the
overlay’s stated contract says the UID has to match because the configured endpoint may belong to
another installation. The added condition nevertheless accepts an empty returned UID.

pkg/argoapi/client.go[156-180]
internal/server/gitops_handlers.go[198-204]
pkg/argoapi/types.go[115-123]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new cross-server identity boundary fails open when either Application UID is empty, allowing unverified health responses to be applied.

## Fix Focus Areas
- internal/server/gitops_handlers.go[202-223]
- pkg/argoapi/client.go[156-180]

## Recommended Fix
Require both the local Application UID and API response UID to be non-empty and exactly equal before applying API health. Treat any missing or mismatched UID as no valid API answer and retain the fallback behavior.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View medium (1)
8. A new cluster waits on the old retry timer ✓ Resolved 🐞 Bug ☼ Reliability
Description
ApplicationHealthCached rejects unconfigured reads while readRetryAfter is in the future, but
dropConnectionLocked does not clear that field when Reset changes Kubernetes context. An
anonymous-read discovery failure on one cluster can therefore prevent the first health lookup
against the next cluster for a retry interval, even when that next cluster permits anonymous Argo
API reads.
Code

internal/argocd/manager.go[R960-963]

+	if unconfigured && m.client == nil && time.Now().Before(m.readRetryAfter) {
+		m.mu.Unlock()
+		return nil, fmt.Errorf("%w: anonymous read retry throttled", ErrUnreachable)
+	}
Relevance

●●● Strong

Recent accepted precedent treats context-switch retry state and stale connection state as
correctness bugs.

PR-#1736

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Failed unconfigured reads arm readRetryAfter, and the added gate returns before discovery while
that timer remains active. Reset calls dropConnectionLocked, which clears the other retry timers
but leaves this new timer untouched despite its comment saying reconnect/context-switch retry state
is cleared.

internal/argocd/manager.go[634-656]
internal/argocd/manager.go[959-972]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

Issue description
The anonymous discovery throttle is cluster-specific connection state but survives a manager reset. A context switch can consequently inherit a refusal or discovery failure from the previous cluster.
Fix Focus Areas
- internal/argocd/manager.go[642-656]
- internal/argocd/manager.go[959-972]
Recommended Fix
Reset `readRetryAfter` in `dropConnectionLocked` with the other retry throttles, so a reconnect or context switch can immediately discover and query the newly selected cluster.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
✅ Compliance rules (platform): 44 rules
✅ Cross-repo context — repo relationships
  Explored: repo: skyhook-dev/radar-hub (sha: 6e33315d)
  Explored: repo: skyhook-dev/radar-e2e (sha: e4918cc2)
  Explored: repo: skyhook-dev/skyhook-connector (sha: b2052d09)
  Explored: repo: skyhook-dev/radar-hub-web (sha: 6e0f4055)
  Explored: repo: skyhook-dev/radar-docs (branch: docs/gitops-argo-api-health, sha: 15259abd)
Review mode: 🧠 Deep: This is a substantial cross-layer behavioral change spanning API clients, caching/throttling, identity validation, health overlay semantics, and UI provenance, with many independent logic paths where redundant review could catch subtle correctness or security-adjacent defects.

Grey Divider

Tip of the day
💡 Did you know, you can ask Qodo to dismiss a finding you disagree with, with your reason on record

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread internal/server/gitops_handlers.go Outdated
Comment thread internal/argocd/manager.go
Comment thread internal/server/gitops_handlers.go Outdated
Comment thread internal/server/gitops_handlers.go Outdated
Comment thread internal/server/gitops_handlers.go Outdated
Comment thread internal/argocd/manager.go
Comment thread internal/argocd/manager.go Outdated
Comment thread internal/server/gitops_handlers.go Outdated

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread internal/argocd/manager.go
Comment thread internal/argocd/manager.go
A tree error is no answer (never the GET's mis-scoped inline health); a
healthless tree is an answer and clears every declared node, whatever it
carried before; an answer must prove identity (empty UID is rejected);
insights prefer the API overlay over a stale inline value; the anonymous
read throttle drops on context switch; the fetch generation is taken
before the probe can fail; a caller's own cancellation is never cached as
a miss; API health strings are normalized to the node vocabulary.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread internal/argocd/manager.go
Comment thread pkg/gitops/insights/insights.go
…d argocd-server certificate

The reachability probe and the anonymous read send no token, so there is
nothing on the wire to protect; skipping TLS verification for them lets the
zero-setup path reach the default in-cluster install. Clients that carry
the token still honour argoCdInsecureTls, and the certificate hint now
appears where the token would first be sent.
@qodo-code-review

Copy link
Copy Markdown

ⓘ Your Qodo trial ends soon. Ask your workspace admin to set up billing to keep reviews running after the trial. Manage billing

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread internal/argocd/manager.go
…wers; the page says when a configured one doesn't

A reachable argocd-server that refuses reads is not an integration: the
manager now hides a tokenless client from Get() until an anonymous read
has succeeded, so Settings never reports Connected on a server that 401s
and the capabilities probe stops firing managed-resources calls through
it. Once anonymous reads work, the connection is the integration — Settings
says "Connected · no token needed" and the diff and revision-metadata gates
accept it.

With the integration configured and no usable answer, the insights summary
carries why (token rejected, requires a token, unreachable, timed out,
another install answering, unknown app) and the notice says so with a link
to Settings instead of quietly showing Radar's read. A configured server
that fails to connect is throttled like unconfigured discovery, so a bad
URL costs one probe per retry interval rather than one per page open.

The notice's "connect Radar to your Argo CD server" clause is host-injected
through the layout's onOpenSettings (standalone opens Settings → Argo CD;
embedded hosts get only the Argo-side knob), and the Argo CD Settings
section now names per-resource health alongside the diff.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread internal/server/gitops_handlers.go Outdated
…nnecting, Settings says why a connection failed, the notice leads with the failure

Connecting Argo CD in Settings now invalidates the GitOps queries, so the
detail page that sent the user there shows Argo's verdicts when they get
back instead of the notice that sent them. The Argo CD status carries the
last probe failure in plain words ("Radar couldn't connect to Argo CD: the
server couldn't be reached."), so the section the notice links to says
what is wrong rather than nothing. When a configured connection didn't
deliver, the notice leads with that and why, and only then says what the
rows below are meanwhile — the user who set it up doesn't need the Argo
CD 3 history first.
…s when reads work anonymously, and starts at the token when nothing is set up
@nadaverell nadaverell changed the title Show Argo's own per-resource verdicts from argocd-server on Argo CD 3 Show Argo's own per-resource verdicts from argocd-server on Argo CD 3, with or without a token Sep 14, 2026

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ae31263. Configure here.

Comment thread internal/argocd/manager.go
@nadaverell
nadaverell merged commit 79134f3 into main Sep 14, 2026
9 checks passed
@nadaverell
nadaverell deleted the feat/argo-api-resource-health branch September 14, 2026 13:44
nadaverell added a commit that referenced this pull request Sep 14, 2026
The Argo CD section in Settings only reported a connection in two cases:
the line that appears right after clicking Connect, and (since #1751)
the anonymous-read case. A working token-backed connection showed
nothing in the pane itself — only the Overview row said "Connected".
Someone opening the section to check on the integration saw a URL field
and a token field and had to go back to Overview to learn it was fine.

The pane now carries the same check-mark line the anonymous case has,
reading "Connected to Argo CD at <address>." whenever the status
endpoint reports a live token-backed connection; the anonymous wording
is unchanged and the two are exclusive. The transient "Connected —
applied, no restart needed" line after a Connect click still takes
precedence.

Testing: `make tsc`. Copy-only addition on an existing pattern; no
visual capture.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Settings UI copy and prop wiring only; no changes to Argo CD
integration APIs or credential handling.
> 
> **Overview**
> The **Argo CD** settings section now surfaces an in-pane success line
when the status API reports a **token-backed** live connection, matching
what Overview already showed.
> 
> A new **`connectedAddress`** prop is passed from `useArgoStatus` (when
`connected` and not `anonymous`) through `ArgoCDConfigField` into
`ArgoCDEditableField`. The existing green check banner (previously only
for anonymous read access) also appears for authenticated connections
with copy **"Connected to Argo CD at …"**; anonymous wording is
unchanged and the two cases stay mutually exclusive. The transient
**"Connected — applied, no restart needed"** line after **Connect &
save** still wins while local apply state is `connected`.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
0c9753d. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant