Repository navigation
Cloud connect: when the in-app install is blocked, say what Radar tried, why it stopped, and what to do - #1772
Conversation
…ty problem The blocked headline, the server's message and the CLI's summary all said "your Kubernetes identity cannot perform this" whenever preflight blocked. Preflight also blocks on an object that exists but is not owned by the Helm release, on admission refusals, and on a chart Secret or manifest Radar could not verify — none of which is about who the caller is, and telling that person to ask for broader permissions sends them the wrong way. Say what actually happened: the dry run was refused or could not be verified; the blocking lines underneath already name which.
Code Review by Qodo🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)
Great, no issues found!Qodo reviewed your code and found no material issues that require reviewTip of the day💡 Did you know, you can reply 'qodo' on any finding to push back, ask questions, or dig deeper |
PR Summary by QodoDescribe blocked Cloud preflights as dry-run failures
AI Description
Diagram
High-Level Assessment
Files changed (3)
|
Preflight records each blocker by cause at the site that finds it: a denied access review or a 403 from the dry run is the caller's identity lacking a permission; a hidden chart Secret is Radar unable to prove the mutation; anything else — admission, an object owned by something else, an API the cluster does not serve — is the cluster refusing the change. Cause() picks one word for the result, with a refusal outranking denials (permission alone would not clear it) and denials outranking verification. The blocked card, the server's message and the CLI summary each present that word: missing permissions → ask an operator, they have them; the cluster refused → an operator has to clear it; can't verify → use the browser wizard. The browser link is offered only where it changes the outcome, so a cluster refusal no longer suggests a path that would meet the same refusal.
Admission webhooks answer 403 too, and more permission would not change
their answer, so only the apiserver's fixed authorizer sentence ("User
cannot <verb> resource") counts as the caller lacking a permission; any
other Forbidden is the cluster refusing the change. The CLI's verification
follow-up no longer sends the operator back through the same preflight the
hidden-Secret marker just blocked; it points at the wizard's Helm command.
Card copy names the cause in one line and says who clears it.
e3f9937 to
5d79325
Compare
The marker preflight looks for is written by Radar's own Helm render of Radar's own chart; it can only appear when a chart version renders something this binary's installer refuses to apply unseen. Blaming "the chart" read as the person's problem. Name it as a Radar version limit and point at the wizard, which installs the same chart with Helm directly.
…, and what to do The card replaces the plan the person never saw, so it now states the plan's facts first — which Helm operation, which release and namespace, as whose identity, and that nothing changed — then why it stopped in the cause's own terms with the refusals underneath, then the one remedy every stop shares: a cluster admin connects it from the browser wizard, which sets the connection up the way the cluster is normally deployed to (Helm, or a values patch for Argo CD or Flux). The wizard link is offered on every blocked card except GitOps, whose remedy lives in the repo. A permission denial while looking for the existing install — Helm keeps release state in Secrets the identity may not be allowed to read — is the same story as a denied preflight and gets the same card; a Kubernetes API error there is Radar failing to inspect, not a refusal, and surfaces as a retryable failure instead of an "unsupported" verdict. The CLI prints the same three parts.
The remedy is a page with three tabs, not a flow, so it is called the install page and the card's one action is a button: "Get the install command". Back sits at the right. Copy trimmed by about a fifth. A binding's NotFound for a prepared role whose own dry run was refused is not recorded as a blocker: the role's refusal already is, and recording the dependent error let it outrank a plain permission denial.
The blocked card's button and the footer's "or set up in the browser" went to /signup, which the Hub does not carry across sign-in: the person landed on Home and had to find the install page. /install is what the Hub stashes and replays after sign-in, and its sign-in card names the pending install. An adopt carries existing=1 with the release and namespace Radar planned; a fresh install takes the page's defaults, which are Radar's defaults too.
When inspection identified an existing release but chart preparation was refused or denied, the blocked response dropped the plan, so the install link opened a fresh install over a release that should be adopted. The target now rides along whether or not the dry run ran.
…ton on unsupported The release Radar planned to adopt now rides on the handoff itself, so the footer's browser link after Back — and after a flow that failed with a plan in hand — adopts the same release the blocked card did instead of opening a fresh install over it. The unsupported card loses the install button: its refusals (several Radars, one already connected, ownership Radar will not guess at) say in the message what to do, and a fresh-install link there would contradict it.
Reading a release's Helm state can be refused after discovery has already found the Radar Deployment carrying it; the error now carries that target so the permissions card still adopts the release instead of offering a fresh install over it. On the frontend, every handoff built from a status with a plan — a failure, a canceled plan — keeps the plan's release the same way the blocked card does.
…emedy The footer's browser link, after Back from a GitOps or unsupported refusal, opened the Hub's fresh install over the very release the card said to recover or leave to the repo. Those handoffs fall back to the signup pitch instead. The adoption dry run now treats a binding's NotFound for a refused role as dependent, the way the fresh path does, so a permission denial is not outranked by its own echo.
A refusal whose remedy is elsewhere never does, whatever release it names. A failure the server marked not safe to retry never does either: a Hub pairing or a Kubernetes install may already exist, its recovery is on the failed card, and a second install would compound it. A blocked preflight and a canceled plan created nothing, so the install page is the way forward.
…e plan it had A target can be known from discovery before any chart was rendered or dry-run. The attempted plan now carries the stage — inspect, prepare, preflight — and the card's "what Radar tried" reads from it, so it never claims a dry run that did not happen.
The card is the one moment Radar has just established what is in the cluster, so its button may point at the install page with the release it found. The footer's standing "or set up in the browser" link is built from whatever the last attempt left behind — a canceled plan, a refusal, a half-finished install, an inspection that never completed — and every one of those states had a case where a fresh-install link was the wrong answer. It stays on the signup pitch, where a wrong guess about the cluster cannot become a fresh install over an existing release.
…ished Reading Helm state after discovery ran establishes the target either way: the release discovery found, or a fresh install where it found none. A denial before discovery finished leaves it unknown, and that card tells the admin to check before installing instead of linking a fresh install that could land on a Radar already there. The fresh link names the default Helm tab so the Hub stashes it across sign-in, which it does only for a link carrying an intent.
…stall Discovery that could only see the default namespace may have missed a Radar elsewhere; "found none" then means nothing, and the card withholds the install link the way it does for a denial before discovery ran.
The plan card discloses an incomplete discovery and asks for an acknowledgement before a fresh install; the blocked card has no such step, so it carries no target and offers no install link in that case.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit a8dc4d9. Configure here.
Discovery can establish the release it found; it can never establish that none exists, because a Helm release outlives a deleted or unlabeled Deployment and the Hub's fresh command would reset its values. Before inspection completes the card points at a release to adopt or at nothing.
A fresh plan from a discovery that saw only the default namespace kept its stage but was sent as no attempt at all, so the card described Radar as still searching above refusals that came from a dry run. The attempt now carries a partialScan flag: the stage is told truthfully, the install link is withheld, and the card says which namespace was checked so the admin knows what to look at before installing.
A stop that leaves the person nowhere to go is a dead end, however well it is explained. Every blocked card now ends the same way — an admin with cluster access, in Radar Cloud — with one button whose destination follows what Radar established: the install page, deep-linked, when it knows the release to adopt (pinned to the Helm tab), the fresh install from a complete plan and a complete scan, or a GitOps-managed release's values patch for its verified controller; Radar Cloud itself everywhere else — an unknown target, an unverified owner, and every unsupported refusal, whose own message says what to resolve or recover. The Hub's fresh command would reset an existing release's values and its script writes the token Secret before Helm runs, so a guessed target is never linked. The GitOps and unsupported cards join the what / why / what-to-do shape, the server message as their why. A prepare error's reason stays on the pitch under the CTA after its toast is gone.
…resh install, and says so When a complete discovery scan finds no Radar running anywhere but reading Helm's release records is refused, the card offers the fresh install command and tells the admin to confirm nothing is installed first — the one thing not ruled out is a leftover release whose Deployment was deleted, and a fresh install over it would reset values for a Radar that was not running anyway. A partial scan, or a denial before discovery finished, still establishes nothing and exits to Radar Cloud.
A Radar Deployment found without matching native-Helm ownership would be refused as unmanaged had inspection succeeded; a denied inspection must not turn it into an adoption link, nor into a fresh install over it. Such a find offers neither and exits to Radar Cloud.

Summary
When the in-app Cloud connect stops before installing, the person sees a card in place of the plan they clicked for. It used to be a one-line verdict — often "your Kubernetes identity can't perform this", sometimes a raw Go error — with no way forward. Now it tells them three things: what Radar tried, why it stopped (in the cause's own terms, with the raw refusals underneath), and the one remedy every stop shares — a cluster admin gets the install command from Radar Cloud's install page, choosing Helm, Argo CD or Flux to match how the cluster is normally deployed to. The CLI prints the same three parts.
What changed
Blockers are classified where they're found (
internal/cloudinstall/preflight.go).PreflightResult.Blockingstays the full list (CLI and existing consumers unchanged) and gains two subsets:Denied— the caller's identity lacks a permission: a denied access review, or a dry-run 403 written in the apiserver's own permission sentences (User "x" cannot <verb> resource …, or RBAC escalation prevention'sis attempting to grant RBAC permissions not currently held). Any other 403 is an admission webhook or policy, and is not a denial — more permission wouldn't change its answer.Unverifiable— Radar can't prove the exact mutation. Today this only fires when Radar's own render of Radar's own chart hides a Secret (the chart renders none in this flow; the guard fails closed against a future chart doing so), so it's presented as a Radar version limitation.Cause()reduces that to one word —clusteroutrankspermissions(permission alone wouldn't clear a refusal),permissionsoutranksverification. A generated RoleBinding's NotFound for a prepared role is never its own blocker: the role's outcome is the real one, and a dependent error must not outrank a permission denial.Errors before the dry run get the same treatment. Looking for the existing install reads Helm state from Secrets the identity may not be allowed to read; that permission denial now gets the permissions card rather than an "unsupported" verdict quoting the error. A Kubernetes API error at that stage is Radar failing to inspect and surfaces as a retryable failure. What remains as
unsupportedis the inspection itself refusing (several Radars, one already connected, ownership Radar won't guess at, an incompatible existing release).The card (
CloudConnectFlow.tsx). The server putscauseand the attempted operation (mode,namespace,release) on the blocked response — additive;reasonand theblocked_*handoff outcomes are unchanged. Three labeled sections, one button, Back at the right:What Radar tried states the Helm install/upgrade, release, namespace and identity, and that nothing changed (or, before a plan existed, that Radar looked for an existing install). What to do: "Have a cluster admin get the install command from Radar Cloud. One page: pick Helm, Argo CD or Flux — whatever this cluster normally uses — and see exactly what it changes before running it." Button: Get the install command. The GitOps card keeps its own instructions (its remedy lives in the repo); every other blocked card, including
unsupported, offers the button.Every blocked card ends with one exit. The button's destination follows what Radar established, never a guess — the Hub's fresh command would reset an existing release's values, and its script writes the token Secret before Helm runs:
/install?existing=1&ns&release&method=helm/install?method=helm/install?existing=1&ns&release&method=argocd|flux— the in-cluster lane's own linkunsupportedrefusal/installis what the Hub stashes across sign-in and replays (it does so for a link carrying a target or amethod, hencemethod=helmon every install link). The copy says what the person will meet: Radar Cloud asks for a cluster name, then shows the instructions to review. The GitOps and unsupported cards use the same what / why / what-to-do shape, their server message as the why. The footer's standing "or set up in the browser" link stays on/signup: it is built from whatever the last attempt left behind, and a wrong guess there must not become an install. A prepare error's reason stays on the pitch under the CTA after its toast is gone.Testing
preflight_test.go:Cause()precedence; a dry-run 403 with admission wording classifies as cluster while the authorizer sentence and both RBAC escalation messages classify as permissions.fresh_preflight_test.go: a denied generated role's binding NotFound leaves a single permissions blockercloudConnectHandoff.test.ts: one assertion per exit row — adopt pinned to Helm; fresh only from a complete plan and scan (partial scan → Radar Cloud); unknown target → Radar Cloud; GitOps → its verified owner's tab, unverified → Radar Cloud; unsupported → Radar Cloud even with a known release.cloud_install_test.go: the GitOps block picks the verified owner when a stale candidate is listed first; an adopt target survives a block after inspection; a fresh plan from a partial scan is flaggedgo test ./internal/cloudinstall/ ./internal/server/ ./cmd/explorer/,make tscview-only ServiceAccount (permissions card from a denial before inspection finished — ends with "Open Radar Cloud" and the admin handoff) and aValidatingAdmissionPolicydenying theradarnamespace as admin (cluster card, correctly not classified as permissions despite arriving asis forbidden:)Note
Medium Risk
Changes install planning, preflight classification, and Hub handoff URLs; wrong exit targeting could suggest a fresh install over an existing release, though tests explicitly guard partial scans and unsupported refusals.
Overview
When in-app Cloud connect stops before installing, the experience is reworked so users see what Radar tried, why it stopped, and a single actionable exit—in the web blocked card and in
radar cloud installCLI output.Preflight and pre-dry-run errors are classified in
internal/cloudinstall: blockers are recorded as permission denials (blockDenied), cluster refusals (blockRefused), or unverifiable chart output (blockUnverifiable), withCause()returningpermissions,cluster, orverification.IsAuthorizationDenialseparates apiserver RBAC 403s from admission/policy forbids. A generated RoleBinding’s dry-runNotFoundno longer masks the parent Role’s permission denial.The server blocked response is richer:
cause,attempted(mode, namespace, release, stage, partial scan, unread Helm state, GitOps method), andReleaseInspectErrorso permission failures while reading Helm Secrets get the permissions card while still deep-linking adopt when discovery found a native Helm release.The UI replaces the generic signup link with
exitFor()—Hub/installdeep links only when the target is established (adopt, safe fresh, verified GitOps tab); otherwise Open Radar Cloud. The blocked card uses three labeled sections and a primary CTA; prepare errors persist on the pitch footer after the toast.Reviewed by Cursor Bugbot for commit 92625e8. Bugbot is set up for automated code reviews on this repo. Configure here.