Skip to content

Opt-in anonymous usage stats - #1879

Merged
nadaverell merged 1 commit into
mainfrom
claude/radar-telemetry-opt-in-a1517d
Sep 27, 2026
Merged

nadaverell merged 1 commit into
mainfrom
claude/radar-telemetry-opt-in-a1517d

Conversation

@roylibman

@roylibman roylibman commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Adds an optional, anonymous usage report that helps us see which parts of Radar people actually use. It is off by default. Nothing is recorded or sent unless you turn it on, and you can see the exact report before you do.

The short version

  • Off unless you say yes. Saying no, or never answering, sends nothing.
  • Anonymous. There's no install ID, account or cluster ID, and nothing that names you, your company or your clusters.
  • No cluster contents. It never includes names of resources, namespaces, clusters, contexts or hosts, and never includes manifests, logs, events or anything you type.
  • You can read it first. Settings > Privacy shows the exact report that would be sent. The pending report also sits in a plain JSON file, ~/.radar/usage-report.json.
  • Easy to turn off for good: the switch in Settings, or the Helm value usageReporting.enabled in a cluster. Turning it off deletes anything pending.
  • Sent straight to us, at usage.radarhq.io. Radar ships no analytics SDK or third-party trackers. The receiver stores each report in our product analytics (PostHog) under a new random ID, without your IP address or its location.

Why

We want to spend our time on what matters to people using Radar. The report answers three questions: which features get used, how teams running Radar in their cluster use it compared with people running it on a laptop, and which integrations matter most.

What a report contains

Once a day, only if you've opted in and only for a day Radar was used:

  • How many times each screen was opened, and how many times each action ran. Actions are named by Radar's own route, never by what they acted on, for example POST /api/helm/releases/{namespace}/{name}/rollback.
  • How many times each MCP tool was called, and which changes failed or which requests Radar itself failed, by route and status class (4xx or 5xx).
  • How often the command palette and search were opened, and which screen crashed, by a fixed screen name such as Topology.
  • Sessions, and active time as a range.
  • The Radar version, OS, how it was installed, whether it runs locally or in a cluster, and a few of its own settings: auth mode, timeline storage, whether MCP and Prometheus are on, cost source and browser family.
  • How many kubeconfig contexts you have, as a range, and for each cluster you used: the Kubernetes minor version (1.33), the platform (eks, gke, kind…), the node count as a range (10-19), and which well-known integrations are installed, from a fixed list in Radar's source (argo-cd, cert-manager…). Anything not on that list, such as your own APIs, is never named.

The full page is docs/usage-stats.md, published at radarhq.io/docs/configuration/usage-stats by skyhook-dev/radar-docs#114. Settings > Privacy links there.

How you're asked

  • A new local install sees one small card in the corner, once.
  • After an upgrade the same one-line question appears in What's New, when a release has notes. "No thanks" is final. If you just close it, it can come back after three months.
  • A shared Radar (installed in a cluster, behind sign-in, or reachable by others) never asks. It stays off unless whoever runs it sets usageReporting.enabled: true in the Helm values. Settings shows the state read-only.
Control Effect
Radar Cloud Always off; Cloud has its own terms
Helm usageReporting.enabled Decides for a whole in-cluster install and locks the switch
Settings > Privacy Your own choice, on a local install

How it's built

  • internal/usagedata is the collector.

    • Recorders: they take only allowlisted names. Views are built-in screens and built-in kinds. Actions are chi route patterns. MCP tools and UI events are pattern-checked. Integrations come from a fixed CRD-group map.
    • Storage and sending: counts are kept in ~/.radar/usage-report.json, and one report goes out a day.
    • Consent: nothing is recorded before consent. Turning it off stops counting and cancels any send in flight before the choice is even saved, then deletes what's pending.
    • Failures: a report the receiver refuses (400 or 413) is dropped; anything else is retried.
    • In-cluster: the pending report lives on the pod's emptyDir, so an in-cluster Radar sends it on shutdown.
  • Server (internal/server/usage_handlers.go):

    • GET/PUT /api/usage-data and a same-origin event endpoint for UI views and sessions;
    • middleware that counts API requests by route pattern;
    • a hook in MCP tool registration.
  • UI:

    • the first-run corner card (UsageDataPrompt);
    • the question in What's New (UsageDataAsk);
    • Settings > Privacy (PrivacySection): the switch, what's sent and what isn't, and the report behind a disclosure.

    Shared and configured installs never ask, and show the switch read-only.

  • Chart: usageReporting.enabled (unset, true or false). There are no new permissions, and Radar writes nothing to the cluster.

  • Docs: docs/usage-stats.md, "What Radar sends" in docs/configuration.md, SECURITY.md, the README and the Helm README.

Testing

  • Go tests, with the usage-data package run under -race. They check:
    • reports carry no ID and none of the excluded fields;
    • nothing is recorded before consent;
    • turning it off stops counting at once and nothing is sent afterwards, even when saving the choice is slow or reading it back fails;
    • a shared install is decided only by its configuration;
    • "No thanks" is never asked again;
    • an in-cluster Radar sends its pending counts when it shuts down;
    • a report the receiver refuses is dropped, while network failures are retried.
  • Frontend: type check and all web tests pass.
  • Visual test: the first-run card (light and dark); What's New with the question, before and after answering, at desktop and phone width; Settings > Privacy on a local install, off and on. The shared-install states are covered by unit tests.
  • End to end: a report built by Radar was accepted by the live receiver at usage.radarhq.io, and stored with no sender IP, location or person.

Related

  • The receiver, skyhook-dev/marketing#311, is merged and live.
  • skyhook-dev/radar-docs#114 publishes the docs page and should merge after this PR, since its content syncs from this branch.
  • UI-editable settings for in-cluster installs are tracked separately in RAD-637.

Note

Medium Risk
Introduces outbound telemetry and consent flows with privacy safeguards, but misconfiguration or bugs could still affect trust; shared installs are operator-controlled via Helm/env.

Overview
Adds opt-in anonymous usage reporting end to end: a new internal/usagedata collector that only records after consent, aggregates allowlisted views/actions/MCP/UI events and coarse cluster shape (no names or install IDs), persists a preview in ~/.radar/usage-report.json, and posts daily to usage.radarhq.io (dev builds log only; in-cluster can flush on pod shutdown).

The server wires this in via /api/usage-data handlers (same-origin guarded), route-pattern API middleware, MCP tool wrappers, and lifecycle start/stop. Helm gains usageReporting.enabled → RADAR_USAGE_REPORTING (skipped when Radar Cloud is on).

The web app adds Settings Privacy (toggle + JSON preview), a delayed first-run corner card, and the question in What's New; it records sessions, screen time, and fixed crash labels when recording is on. Docs/security/README now describe the third outbound request type and what is (and is not) sent.

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

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Add opt-in anonymous usage telemetry

✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Adds strictly opt-in daily usage reporting with allow-listed, anonymized, bucketed data.
• Exposes consent prompts, Privacy settings, report previews, and deployment-level controls.
• Persists shared choices through scoped Helm RBAC and comprehensive backend/frontend tests.
Diagram

sequenceDiagram
    actor User
    participant Web as Radar Web
    participant API as Server API
    participant Collector as Telemetry Collector
    participant Choice as Choice Store
    participant K8s as Kubernetes API
    participant Pending as Pending Report
    participant Relay as Skyhook Relay
    User->>Web: Choose consent
    Web->>API: Update choice
    API->>Collector: Apply decision
    Collector->>Choice: Persist choice
    loop Only while opted in
        Web->>API: Allow-listed events
        API->>Collector: Record counts
        Collector->>K8s: Sample cluster shape
        Collector->>Pending: Save report preview
    end
    Collector->>Relay: Send daily report
Loading
High-Level Assessment

The first-party, explicit opt-in approach is appropriate for the privacy and trust requirements. Third-party analytics SDKs, opt-out collection, and server-only consent were considered implicitly by the design but would weaken payload transparency, control precedence, or data minimization; the current allow-listed collector with an exact preview is preferable.

Files changed (38) +3817 / -14

Enhancement (20) +2509 / -8
toolparams.goCount allow-listed MCP tool calls +7/-1

Count allow-listed MCP tool calls

• Wraps registered MCP handlers so each invoked tool name is recorded by the telemetry collector before execution.

internal/mcp/toolparams.go

server.goIntegrate telemetry into the server lifecycle +14/-0

Integrate telemetry into the server lifecycle

• Registers telemetry middleware and API routes, starts collection with the server, and cancels and flushes it during shutdown.

internal/server/server.go

telemetry_handlers.goAdd telemetry APIs, sampling, and request instrumentation +326/-0

Add telemetry APIs, sampling, and request instrumentation

• Wires collector options, gathers coarse setup and cluster facts, counts matched API routes, and exposes status, consent, prompt, and UI-event endpoints. Consent and events require same-origin requests, while event names are server allow-listed.

internal/server/telemetry_handlers.go

usage_store.goPersist shared telemetry choices in Kubernetes +185/-0

Persist shared telemetry choices in Kubernetes

• Implements conflict-safe ConfigMap persistence for consent, prompt state, and identity. It verifies write access before trusting stored state and falls back to pod-local settings when unavailable.

internal/server/usage_store.go

settings.goStore telemetry consent and anonymous identity +27/-0

Store telemetry consent and anonymous identity

• Extends settings with the usage choice, decision metadata, first-prompt timestamp, random install ID, and private cluster-ID salt.

internal/settings/settings.go

record.goImplement privacy-bounded usage recorders +209/-0

Implement privacy-bounded usage recorders

• Adds allow-listed recording for views, UI events, MCP tools, browser families, active time, actions, and route-classified failures. Free-form names and telemetry's own API traffic are rejected.

internal/telemetry/record.go

report.goDefine and anonymize the usage report +306/-0

Define and anonymize the usage report

• Defines the report schema and pending counters, converts counts and versions to coarse values, masks custom builds, and generates random install identities and salted cluster IDs.

internal/telemetry/report.go

telemetry.goImplement opt-in telemetry collection and delivery +752/-0

Implement opt-in telemetry collection and delivery

• Adds control precedence, consent state management, pending report persistence, daily scheduling, retries, report previews, prompt eligibility, and first-party delivery. Opt-out cancels in-flight sends and deletes accumulated data and identity.

internal/telemetry/telemetry.go

version.goExpose safe installation metadata +23/-0

Expose safe installation metadata

• Exports installation method, build channel, local installation time, and development-build detection for report construction and prompt eligibility.

internal/version/version.go

crd_groups.goExpose curated integration lookup +9/-0

Expose curated integration lookup

• Adds a safe lookup from known CRD API groups to integration names so unknown customer-defined groups are never reported.

pkg/packages/crd_groups.go

App.tsxConnect telemetry prompts and view recording +20/-1

Connect telemetry prompts and view recording

• Loads telemetry status outside embedded hosts, maps routes to safe view names, starts usage recording, and supplies consent state to first-run and What's New prompts.

web/src/App.tsx

telemetry.tsAdd telemetry client hooks and event recording +163/-0

Add telemetry client hooks and event recording

• Defines report and status types, query and mutation hooks, prompt acknowledgement, and guarded fire-and-forget event submission. The recording hook counts views, sessions, and visible-time intervals only while enabled.

web/src/api/telemetry.ts

PrivacySection.tsxAdd usage controls and exact report preview +177/-0

Add usage controls and exact report preview

• Adds a Privacy pane with the consent switch, managed-state explanations, shared decision metadata, persistence warnings, documentation link, and exact next-report JSON.

web/src/components/settings/PrivacySection.tsx

SettingsDialog.tsxAdd Privacy to Settings navigation +13/-3

Add Privacy to Settings navigation

• Registers a Privacy section available independently of operator-managed configuration and renders its live telemetry controls.

web/src/components/settings/SettingsDialog.tsx

settings-state.tsAdd the Privacy settings section type +1/-0

Add the Privacy settings section type

• Extends the settings section identifier union with privacy.

web/src/components/settings/settings-state.ts

ErrorBoundary.tsxCount UI crashes by component +4/-0

Count UI crashes by component

• Records only the parsed component name when an error boundary catches a crash, excluding error messages and stack contents.

web/src/components/ui/ErrorBoundary.tsx

Omnibar.tsxCount command palette use without queries +16/-0

Count command palette use without queries

• Records palette openings and whether a search occurred once per opening, without recording the search text.

web/src/components/ui/Omnibar.tsx

UsageDataAsk.tsxAdd the What's New consent question +81/-0

Add the What's New consent question

• Provides reusable consent wording and an inline What's New prompt with explicit accept, reject, and report-details actions.

web/src/components/usage-data/UsageDataAsk.tsx

UsageDataPrompt.tsxAdd the one-time first-install prompt +158/-0

Add the one-time first-install prompt

• Shows a delayed corner consent card once per machine, avoids overlapping the update notice, records that it was shown, and links to Privacy details.

web/src/components/usage-data/UsageDataPrompt.tsx

WhatsNew.tsxEmbed consent in the What's New dialog +18/-3

Embed consent in the What's New dialog

• Accepts telemetry status, renders the reusable consent question outside the scroll area, and routes report details to Settings > Privacy.

web/src/components/whats-new/WhatsNew.tsx

Tests (10) +1165 / -0
deployment_telemetry_test.yamlTest deployment telemetry environment rendering +47/-0

Test deployment telemetry environment rendering

• Verifies unset, enabled, disabled, and Radar Cloud behavior for RADAR_TELEMETRY in the rendered Deployment.

deploy/helm/radar/tests/deployment_telemetry_test.yaml

usage_data_configmap_test.yamlTest scoped ConfigMap resources +37/-0

Test scoped ConfigMap resources

• Checks empty ConfigMap creation, resource-name-scoped permissions, operator-managed RBAC behavior, and Cloud exclusion.

deploy/helm/radar/tests/usage_data_configmap_test.yaml

telemetry_handlers_test.goTest telemetry endpoint protections +92/-0

Test telemetry endpoint protections

• Covers cross-site rejection, explicit consent validation, event allow-lists, JSON requirements, and middleware pass-through while collection is disabled.

internal/server/telemetry_handlers_test.go

usage_store_test.goTest shared choice persistence and fallback +108/-0

Test shared choice persistence and fallback

• Verifies choices survive pod recreation, unrelated settings remain untouched, missing ConfigMaps fall back locally, and read-only ConfigMaps are never trusted.

internal/server/usage_store_test.go

record_test.goTest telemetry classification and anonymization +193/-0

Test telemetry classification and anonymization

• Tests safe route classification, fixed vocabularies, coarse buckets, browser detection, identity rotation, cluster sampling, and per-install cluster unlinkability.

internal/telemetry/record_test.go

telemetry_test.goTest telemetry state, lifecycle, and concurrency +496/-0

Test telemetry state, lifecycle, and concurrency

• Covers control precedence, no collection before consent, opt-out deletion, daily sends and retries, development builds, race-sensitive transitions, prompts, shared consent, and identity persistence failures.

internal/telemetry/telemetry_test.go

telemetry.fixtures.tsAdd telemetry UI fixtures +34/-0

Add telemetry UI fixtures

• Provides representative report and status factories for frontend component tests.

web/src/api/telemetry.fixtures.ts

PrivacySection.test.tsxTest Privacy settings states and wording +83/-0

Test Privacy settings states and wording

• Covers disabled and enabled previews, managed controls, shared-install attribution, pod-local warnings, and log-only behavior.

web/src/components/settings/PrivacySection.test.tsx

UsageDataAsk.test.tsxTest the reusable consent question +40/-0

Test the reusable consent question

• Verifies local and shared wording and ensures the question disappears after a decision or under managed controls.

web/src/components/usage-data/UsageDataAsk.test.tsx

UsageDataPrompt.test.tsTest first-run prompt eligibility +35/-0

Test first-run prompt eligibility

• Verifies the prompt waits for its delay and free corner, and respects dismissal of the competing update notice.

web/src/components/usage-data/UsageDataPrompt.test.ts

Documentation (4) +70 / -6
README.mdClarify opt-in usage statistics in privacy promise +1/-1

Clarify opt-in usage statistics in privacy promise

• Updates the privacy overview to distinguish protected cluster contents from optional anonymous usage statistics and links to the full payload documentation.

README.md

SECURITY.mdDocument telemetry privacy and emergency opt-out +2/-1

Document telemetry privacy and emergency opt-out

• Explains that collection requires consent, summarizes the anonymous daily payload, and identifies DO_NOT_TRACK as the strongest disable control.

SECURITY.md

README.mdDocument the Helm telemetry setting +1/-0

Document the Helm telemetry setting

• Adds the three-way telemetry.enabled value, its switch-locking behavior, and the scoped ConfigMap persistence model to the chart reference.

deploy/helm/radar/README.md

configuration.mdDocument usage reports and controls +66/-4

Document usage reports and controls

• Documents the complete report schema, prohibited data, consent prompts, local persistence, control precedence, delivery behavior, and shared-install storage semantics.

docs/configuration.md

Other (4) +73 / -0
deployment.yamlInject in-cluster telemetry configuration +10/-0

Inject in-cluster telemetry configuration

• Provides the usage ConfigMap name to non-Cloud deployments and translates explicit Helm boolean choices into managed RADAR_TELEMETRY values.

deploy/helm/radar/templates/deployment.yaml

usage-data.yamlCreate scoped usage-choice storage and RBAC +46/-0

Create scoped usage-choice storage and RBAC

• Creates an empty per-release ConfigMap plus a Role and RoleBinding limited to get, update, and patch operations on that ConfigMap. Radar Cloud and operator-managed RBAC are excluded.

deploy/helm/radar/templates/usage-data.yaml

values.schema.jsonValidate the telemetry Helm value +7/-0

Validate the telemetry Helm value

• Adds a telemetry object whose enabled property accepts a boolean or null and rejects unknown fields.

deploy/helm/radar/values.schema.json

values.yamlAdd the default telemetry Helm configuration +10/-0

Add the default telemetry Helm configuration

• Defines telemetry.enabled as null by default and documents team-controlled, locked-on, locked-off, and DO_NOT_TRACK behavior.

deploy/helm/radar/values.yaml

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (2) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (2) 📜 Skill insights (0)

Grey Divider


Action required

1. Embedded activity can still be recorded ✓ Resolved 🐞 Bug ≡ Correctness
Description
AppInner disables the usage-data query for embedded hosts but still passes any cached
usageData.data into useUsageRecording. If Settings > Privacy populates the shared query cache
with an enabled status, the embedded UI begins emitting views, sessions, active time, and subsequent
global events despite the embedded-mode guard.
Code

web/src/App.tsx[R445-447]

+  // Opt-in usage data. Embedded hosts own their own consent, so Radar never asks there.
+  const usageData = useUsageData(!navCustomization.embedded)
+  useUsageRecording(usageView(location.pathname, mainView, upgradeReadinessRoute), usageData.data)
Evidence
The app's disabled observer and PrivacySection use the same query key, while PrivacySection can
refetch it independently. useUsageRecording treats any cached state: on value as permission and
sets the module-wide recording flag before emitting events.

web/src/App.tsx[445-447]
web/src/api/usage-data.ts[56-68]
web/src/api/usage-data.ts[145-164]
web/src/components/settings/PrivacySection.tsx[9-16]
web/src/components/settings/SettingsDialog.tsx[803-807]

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

## Issue description
Embedded mode disables fetching but can still consume cached enabled usage status, causing embedded activity to be recorded.
## Fix Focus Areas
- web/src/App.tsx[445-447]
- web/src/api/usage-data.ts[145-164]
## Recommended Fix
Pass an explicitly non-recording status to `useUsageRecording` whenever `navCustomization.embedded` is true, rather than relying on a disabled query having no cached data. Also reset the module-level recording flag during hook cleanup so unmounting or changing host mode cannot leave recording enabled.

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


2. Stale consent can start collection ✓ Resolved 🐞 Bug ⛨ Security
Description
checkWritable treats an access-review error as retryable, but Load then reads and trusts the
ConfigMap instead of failing closed. When that review fails while the existing broad role still
permits ConfigMap reads, a stale enabled choice starts recording even though Radar has not confirmed
that a later opt-out can be persisted.
Code

internal/server/usage_store.go[R100-101]

+	if err != nil {
+		return // try again next time; a transient failure must not decide
Evidence
The access-review error path returns without setting fallback, after which Load continues to fetch
and decode the ConfigMap. Radar's main ClusterRole independently grants read access to all
ConfigMaps, while write access is supplied only by the new named Role, so review failure and
successful stale reads can coexist.

internal/server/usage_store.go[82-105]
internal/server/usage_store.go[109-134]
deploy/helm/radar/templates/clusterrole.yaml[10-19]
deploy/helm/radar/templates/usage-data.yaml[24-28]

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 usage store trusts persisted consent when its write-access review fails, violating the requirement that the ConfigMap is used only after write access is confirmed.
## Fix Focus Areas
- internal/server/usage_store.go[87-128]
- internal/server/usage_store_test.go[91-108]
## Recommended Fix
Make the writability check return a result or error and prevent `Load` from reading the ConfigMap unless the review explicitly returns `Allowed`. On review errors, keep telemetry off and retry validation later without honoring ConfigMap consent; add a test where the access review errors but ConfigMap reads succeed.

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


3. Usage reports always fail ✓ Resolved 🔗 Cross-repo conflict ≡ Correctness
Description
postReport sends production reports to https://releases.skyhook.io/radar/usage, but marketing's
route gate accepts only /radar/latest and returns 404 for every other release-host path. Every
opted-in release build therefore retains and retries its report daily instead of delivering it until
the marketing repository is deployed with a matching receiver.
Code

internal/telemetry/telemetry.go[32]

+	Endpoint = "https://releases.skyhook.io/radar/usage"
Evidence
The PR defines and posts to the new usage URL, while the marketing repository explicitly permits
only the existing latest-release route and rejects all other paths.

internal/telemetry/telemetry.go[30-32]
internal/telemetry/telemetry.go[669-693]
External repo: skyhook-dev/marketing, src/proxy.ts [134-149]

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

## Issue description
Production telemetry posts to `/radar/usage`, but the repository serving `releases.skyhook.io` explicitly returns 404 for that path.
## Fix Focus Areas
- internal/telemetry/telemetry.go[30-32]
- /cross_repos/marketing/src/proxy.ts[134-149]
## Recommended Fix
Coordinate a marketing change that adds a POST receiver and routes `/radar/usage` to it before releasing this sender. If that cannot be deployed first, gate the production sender until the receiver is available.

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



Remediation recommended

4. A subheading comment repeats its code ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
SubHeading is preceded by // Light subheading separating field groups inside a settings pane.,
which translates the component name and rendered `` styling into prose. Any later rename or style
change now has redundant text to synchronize, while deleting the comment preserves all available
intent and constraints.
Code

web/src/components/settings/controls.tsx[4]

+// Light subheading separating field groups inside a settings pane.
Evidence
Rule 3036542 prohibits newly added comments that only restate immediately apparent behavior. The
cited comment describes a subheading in the settings controls module, and the adjacent SubHeading
name and `` implementation already communicate that information.

Rule 3036542: Avoid explanatory comments that restate obvious code behavior
web/src/components/settings/controls.tsx[4-7]

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 comment above `SubHeading` merely restates the component name, its settings context, and the immediately visible heading markup.
## Fix Focus Areas
- web/src/components/settings/controls.tsx[4-4]
## Recommended Fix
Remove the redundant comment while leaving the self-explanatory component implementation unchanged.

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


5. Attackers can pollute usage reports ✓ Resolved 🐞 Bug ⛨ Security
Description
RecordAPI validates the route pattern but concatenates the unvalidated method supplied from
r.Method into action and error keys. A client sending nonstandard methods to matched routes can
therefore inject arbitrary token strings into pending and transmitted reports, bypassing the
promised fixed vocabulary and creating unbounded telemetry cardinality.
Code

internal/usagedata/record.go[R125-130]

+func RecordAPI(method, pattern string, status int) {
+	if !routePattern.MatchString(pattern) || strings.HasPrefix(pattern, "/api/usage-data") {
+		return
+	}
+	key := method + " " + pattern
+	read := method == http.MethodGet || method == http.MethodHead
Evidence
The middleware passes the request's raw method into RecordAPI, which validates only the route and
persists the resulting key in report maps. The middleware is installed before request-protection
middleware, so rejected requests also reach this recording path.

internal/usagedata/record.go[120-139]
internal/server/usage_handlers.go[134-148]
internal/server/server.go[441-445]
internal/usagedata/report.go[107-119]
internal/usagedata/report.go[231-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
Untrusted HTTP method strings are incorporated into usage report keys without validation.
## Fix Focus Areas
- internal/usagedata/record.go[125-139]
- internal/server/usage_handlers.go[134-148]
## Recommended Fix
Reject methods outside a fixed allowlist of Radar-supported HTTP methods before constructing the action key. Add tests proving nonstandard methods are ignored for both successful and failed matched routes.

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


6. Usage sampling scans clusters too often ✓ Resolved 🐞 Bug ➹ Performance
Description
sampleCluster invokes opts.ClusterSample before checking whether that cluster was sampled within
the fifteen-minute interval. Because the collector ticks every minute, opted-in installations
repeatedly list cached nodes, pods, and namespaces and process discovery resources every minute,
with discovery network refreshes possible every five minutes.
Code

internal/usagedata/usagedata.go[588]

+	key, shape, ok := c.opts.ClusterSample()
Evidence
The one-minute tick calls the callback unconditionally, while the fifteen-minute comparison occurs
only after it returns. The callback calls GetClusterInfo and integration discovery; those paths
scan cached cluster collections and periodically refresh API discovery.

internal/usagedata/usagedata.go[36-48]
internal/usagedata/usagedata.go[561-579]
internal/usagedata/usagedata.go[581-607]
internal/server/usage_handlers.go[67-100]
internal/server/usage_handlers.go[152-170]
internal/k8s/cluster_detection.go[59-90]
pkg/k8score/discovery.go[402-477]

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 sampling interval is checked only after the expensive cluster callback has already completed, causing full cache scans every minute.
## Fix Focus Areas
- internal/usagedata/usagedata.go[574-607]
- internal/server/usage_handlers.go[67-100]
- internal/server/namespace_scope.go[178-184]
## Recommended Fix
Track the next permitted cluster-sampling time before invoking `ClusterSample` and return early until it expires. Invalidate that throttle during context switches so a newly selected cluster is sampled immediately, then add a test asserting the callback runs at most once per interval.

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


View medium (12)
7. Refused reports can return after restart ✓ Resolved 🐞 Bug ☼ Reliability
Description
maybeSend drops a report refused with status 400 or 413 in memory but returns without flushing the
replacement pending state. If the process stops before the next periodic flush, readPending
reloads the stale file on restart and retries the report that the receiver explicitly rejected.
Code

internal/usagedata/usagedata.go[R646-649]

+				if c.gen == gen && errors.Is(err, errReportRefused) {
+					log.Printf("[usage] Usage report dropped: %v", err)
+					c.mu.Unlock()
+					return
Evidence
Each normal tick persists the old pending report before sending it. The refusal branch resets memory
and returns before the only post-send flush, while startup unconditionally reloads whatever remains
in the file.

internal/usagedata/usagedata.go[574-579]
internal/usagedata/usagedata.go[628-668]
internal/usagedata/usagedata.go[706-719]
internal/usagedata/usagedata.go[725-740]

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

## Issue description
Reports rejected as permanently invalid remain in the pending file until a later tick and can be resent after a crash.
## Fix Focus Areas
- internal/usagedata/usagedata.go[637-668]
- internal/usagedata/usagedata.go[706-740]
## Recommended Fix
After handling `errReportRefused`, clear the active send state, release the collector lock, and immediately flush the replacement pending period before returning. Add a restart test that refuses a report, reloads the pending file immediately, and verifies the rejected counts are absent.

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


8. Shutdown can leave collection running 🐞 Bug ☼ Reliability
Description
Server.Stop cancels the usage context and immediately runs usagedata.Shutdown without waiting
for the collector's run goroutine to exit. When cancellation arrives during refresh, cluster
sampling, or flushing, that tick continues after shutdown and can write state or contend with a
replacement collector using the same pending file.
Code

internal/server/server.go[R1238-1241]

+	if s.stopUsageData != nil {
+		s.stopUsageData()
+	}
+	usagedata.Shutdown()
Evidence
Cancellation is observed only by the outer loop's next select; tick executes refresh, sampling,
flushing, and sending synchronously without another cancellation boundary. Shutdown performs its
own send and flush but has no join mechanism for that goroutine.

internal/server/server.go[1235-1244]
internal/usagedata/usagedata.go[561-579]
internal/usagedata/usagedata.go[757-770]

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

## Issue description
Canceling the collector does not wait for an already-running tick, allowing usage work to continue after server shutdown.
## Fix Focus Areas
- internal/server/server.go[1235-1244]
- internal/usagedata/usagedata.go[561-579]
- internal/usagedata/usagedata.go[757-770]
## Recommended Fix
Give the collector a completion signal or explicit stop method that cancels its context and waits for `run` to exit. Call that before the final shutdown send and flush, and propagate cancellation into cluster sampling where practical so shutdown remains bounded.

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


9. Telemetry status skips readiness checks ⊘ Outdated 📘 Rule violation ☼ Reliability
Description
handleGetTelemetry calls telemetry.CurrentStatus() without first invoking
s.requireConnected(w). A status request can consequently reach clusterSample, the Kubernetes
resource cache, and k8s.GetClusterInfo before the standard connectivity guard can return a
service-unavailable response.
Code

internal/server/telemetry_handlers.go[235]

+	s.writeJSON(w, telemetry.CurrentStatus())
Evidence
Rule 3036634 requires every cluster-touching handler to call s.requireConnected before cluster
access. The new handler directly obtains current telemetry status, while its configured sample
function reads the Kubernetes cache and cluster information.

Rule 3036634: Cluster-touching HTTP handlers must enforce connectivity with requireConnected
internal/server/telemetry_handlers.go[234-236]
internal/server/telemetry_handlers.go[81-92]

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 telemetry status handler samples cluster state without first enforcing the server's standard connectivity check.
## Fix Focus Areas
- internal/server/telemetry_handlers.go[234-236]
## Recommended Fix
Call `s.requireConnected(w)` at the beginning of `handleGetTelemetry` and return immediately when it reports that the cluster is unavailable.

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


10. Telemetry failures break log format ⊘ Outdated 📘 Rule violation ◔ Observability
Description
handlePutTelemetry and handleTelemetryPromptShown log their HTTP 500 errors with only %v,
omitting the required %s/%s: %v structure. When either usage-data persistence operation fails, its
log entry does not conform to the standardized module-and-action format used for server-error
diagnostics.
Code

internal/server/telemetry_handlers.go[R262-263]

+		log.Printf("[telemetry] Failed to save usage-data choice: %v", err)
+		s.writeError(w, http.StatusInternalServerError, err.Error())
Evidence
Rule 3036628 requires every 500 path to log first with a literal beginning [module] Failed to and
the placeholder shape %s/%s: %v. Both new persistence-failure paths log before responding, but
neither includes the required two string placeholders.

Rule 3036628: Log 500 errors with standardized module/action format before writing the response
internal/server/telemetry_handlers.go[261-264]
internal/server/telemetry_handlers.go[274-277]

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 telemetry handlers return HTTP 500 after log calls that do not use the required standardized placeholder structure.
## Fix Focus Areas
- internal/server/telemetry_handlers.go[261-263]
- internal/server/telemetry_handlers.go[274-276]
## Recommended Fix
Update both pre-response log calls to begin with `[telemetry] Failed to`, include the applicable operation identifiers in `%s/%s`, and finish with the triggering error through `%v`.

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


11. Usage saves show no success toast ⊘ Outdated 📘 Rule violation ⚙ Maintainability
Description
useSetUsageData defines mutation metadata with errorMessage but omits the required
successMessage. Every privacy switch and usage-data prompt uses this mutation, so the centralized
React Query toast handler has no success text after saving the choice.
Code

web/src/api/telemetry.ts[92]

+    meta: { errorMessage: 'Failed to save usage data choice' },
Evidence
Rule 3036648 requires every changed React Query mutation to provide both errorMessage and
successMessage at the top level of meta. The newly added mutation provides only the error
message.

Rule 3036648: React Query mutations must define toast metadata and avoid per-mutation toast handlers
web/src/api/telemetry.ts[82-96]

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 usage-data mutation metadata supplies only an error message, leaving the shared success-toast path without a message.
## Fix Focus Areas
- web/src/api/telemetry.ts[82-96]
## Recommended Fix
Add an appropriate `successMessage` alongside `errorMessage` in the mutation's `meta` object while retaining `onSuccess` only for updating cached status.

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


12. New prompts ignore background themes 📘 Rule violation ⚙ Maintainability
Description
The new switch and usage-data prompts use bg-skyhook-600, bg-white, and bg-accent-muted
instead of the approved background tokens. These classes occur in PrivacySection, UsageDataAsk,
and UsageDataPrompt, leaving the newly introduced controls outside the theme background contract.
Code

web/src/components/settings/PrivacySection.tsx[164]

+          value ? 'bg-skyhook-600' : 'bg-theme-elevated border border-theme-border',
Evidence
Rule 3036653 permits only four theme background utility tokens in modified frontend code. The new
controls introduce two non-theme backgrounds in the privacy switch and bg-accent-muted in both
prompt implementations.

Rule 3036653: Use theme background tokens instead of hardcoded utility color classes
web/src/components/settings/PrivacySection.tsx[162-170]
web/src/components/usage-data/UsageDataAsk.tsx[52-66]
web/src/components/usage-data/UsageDataPrompt.tsx[103-113]

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

## Issue description
New usage-data controls use background utilities outside the checklist's approved theme-token set.
## Fix Focus Areas
- web/src/components/settings/PrivacySection.tsx[162-170]
- web/src/components/usage-data/UsageDataAsk.tsx[52-66]
- web/src/components/usage-data/UsageDataPrompt.tsx[96-113]
## Recommended Fix
Replace `bg-skyhook-600`, `bg-white`, and `bg-accent-muted` with suitable approved tokens from `bg-theme-base`, `bg-theme-surface`, `bg-theme-elevated`, and `bg-theme-hover`, preserving necessary contrast through theme text and border tokens.

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


13. The switch thumb uses a raw shadow 📘 Rule violation ⚙ Maintainability
Description
The new Switch thumb applies Tailwind's raw shadow-sm utility rather than an approved theme
shadow token. This newly introduced privacy control therefore bypasses the shared shadow scale that
later theme changes rely upon.
Code

web/src/components/settings/PrivacySection.tsx[170]

+            'absolute top-0.5 left-0.5 w-4 h-4 rounded-full bg-white transition-transform shadow-sm',
Evidence
Rule 3036687 prohibits raw Tailwind shadow classes in modified components and permits
shadow-theme-sm, shadow-theme-md, or shadow-theme-lg. The newly added switch thumb contains
shadow-sm.

Rule 3036687: Use theme shadow utility tokens instead of raw Tailwind shadow classes
web/src/components/settings/PrivacySection.tsx[168-172]

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 privacy switch thumb uses a raw Tailwind shadow utility instead of the approved theme shadow scale.
## Fix Focus Areas
- web/src/components/settings/PrivacySection.tsx[168-172]
## Recommended Fix
Replace `shadow-sm` with the corresponding approved token, normally `shadow-theme-sm`, and keep the remaining switch-thumb styling unchanged.

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


14. Failure-only days lose their counts ✓ Resolved 🐞 Bug ≡ Correctness
Description
pending.empty ignores the Errors map when deciding whether a period contains reportable use. A
day containing only failed API requests is treated as empty, so maybeSend resets the period
without sending those failures and they never reach the usage report.
Code

internal/telemetry/report.go[R132-136]

+// empty means nobody used Radar in the period; cluster shape alone is not use.
+func (p *pending) empty() bool {
+	return len(p.Views) == 0 && len(p.Actions) == 0 && len(p.MCPTools) == 0 &&
+		len(p.UIEvents) == 0 && p.Sessions == 0
+}
Evidence
RecordAPI stores qualifying server and mutation failures in p.Errors, but pending.empty checks
every primary usage category except that map. maybeSend sends only when taken.empty() is false
and otherwise replaces the pending period, proving that failure-only data is lost.

internal/telemetry/record.go[124-134]
internal/telemetry/report.go[117-136]
internal/telemetry/telemetry.go[621-657]

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

## Issue description
Periods containing recorded failures but no views, actions, tools, UI events, or sessions are discarded as empty.
## Fix Focus Areas
- internal/telemetry/report.go[132-136]
- internal/telemetry/telemetry.go[631-657]
- internal/telemetry/telemetry_test.go[213-224]
## Recommended Fix
Include `len(p.Errors) != 0` in the non-empty decision, and add a send-cycle test proving that an API failure by itself produces a report containing the expected error count.

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


15. Upgrades stop asking for consent ✓ Resolved 🐞 Bug ≡ Correctness
Description
WhatsNew renders UsageDataAsk only inside the release dialog, while close() unconditionally
records the current version as seen without receiving any indication that the telemetry choice
remains unanswered. When an upgraded user dismisses the dialog without choosing an option, the
stored version suppresses it on later startups, so the consent surface stops appearing until another
upgrade even though the status remains undecided.
Code

web/src/components/whats-new/WhatsNew.tsx[156]

+          ask={<UsageDataAsk usageData={usageData} onReadMore={readAboutUsageData} />}
Evidence
The citations show that the consent component is supplied only through the dialog’s ask prop,
while the existing close handler always writes the current version to local storage. Because the
automatic-open effect checks that last-seen version to suppress the dialog, and UsageDataAsk does
not communicate an unanswered dismissal back to WhatsNew, its repeated eligibility while consent
remains undecided cannot affect subsequent startup behavior.

web/src/components/whats-new/WhatsNew.tsx[105-156]
web/src/components/usage-data/UsageDataAsk.tsx[24-50]
web/src/components/whats-new/WhatsNew.tsx[19-29]
web/src/components/whats-new/WhatsNew.tsx[98-109]
web/src/components/whats-new/WhatsNew.tsx[123-143]
web/src/components/usage-data/UsageDataAsk.tsx[24-35]

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 usage-data opt-in question is embedded exclusively in the one-shot What's New dialog, whose close handler marks the release as seen even when the user has not answered. Dismissing the release notes can therefore stop the consent question from appearing for that release without recording either consent or refusal.
## Fix Focus Areas
- web/src/components/whats-new/WhatsNew.tsx[96-156]
- web/src/components/usage-data/UsageDataAsk.tsx[24-50]
## Recommended Fix
Track unanswered telemetry consent separately from the release-notes seen marker. Continue offering an appropriate upgrade consent surface while the server status remains `undecided`, without repeatedly presenting release notes that have already been seen; suppress the consent question only after the telemetry mutation succeeds or the server status is no longer `undecided`. Add coverage for dismissing the dialog without answering and for choosing either consent option.

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


16. Local users can read pending reports ⊘ Outdated 🐞 Bug ⛨ Security
Description
writeAtomic creates the pending usage-report.json file with mode 0644, and its parent
directory is created as 0755. Once someone opts in and telemetry flushes data, other local
accounts able to traverse the home directory can read the report's usage counts, action routes,
browser data, and cluster summaries.
Code

internal/telemetry/telemetry.go[R731-737]

+func writeAtomic(path string, data []byte) error {
+	if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
+		return err
+	}
+	tmp := path + ".tmp"
+	if err := os.WriteFile(tmp, data, 0o644); err != nil {
+		return err
Evidence
The new pending state persists usage maps, browser counts, and cluster shapes. The flush path
serializes that state through writeAtomic, which explicitly creates the directory as 0755 and
the temporary file as 0644 before renaming it into place.

internal/telemetry/telemetry.go[711-743]
internal/telemetry/report.go[117-130]

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 pending telemetry report is privacy-sensitive opt-in data, but it is persisted in a world-readable file beneath a world-traversable directory. Other local accounts can therefore read the report when they can access the user's home directory.
### Fix Focus Areas
- internal/telemetry/telemetry.go[731-743]
- internal/telemetry/report.go[117-130]
### Recommended Fix
Create the telemetry directory and temporary report file with owner-only permissions, such as `0700` for the directory and `0600` for the file. Also ensure an existing report file is tightened as needed before or after atomic replacement.

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


17. Website misstates outbound data 🔗 Cross-repo conflict ≡ Correctness
Description
Report adds opt-in usage, setup, and cluster-shape data beyond the existing version check. When an
OSS user opts in, marketing pages that promise no telemetry and say outbound traffic is limited to
version checks become inaccurate.
Code

docs/configuration.md[R454-455]

+- **Usage data, only if you opt in**: once a day, to
+  `releases.skyhook.io/radar/usage`. See [Usage data](#usage-data).
Evidence
Radar now documents a separate daily usage request and defines cluster metadata in its payload,
directly contradicting marketing copy that says there is no telemetry and only update checks leave
the installation.

docs/configuration.md[454-455]
internal/telemetry/report.go[60-80]
External repo: skyhook-dev/marketing, src/app/radar/product/page.tsx [353-359]
External repo: skyhook-dev/marketing, src/app/radar/product/page.tsx [563-565]
External repo: skyhook-dev/marketing, src/components/radar/RadarOssSignal.tsx [29-35]

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 marketing site still promises no telemetry and version-check-only outbound traffic, although opted-in Radar installations now send daily usage reports.
## Fix Focus Areas
- docs/configuration.md[454-455]
- /cross_repos/marketing/src/app/radar/product/page.tsx[353-359]
- /cross_repos/marketing/src/app/radar/product/page.tsx[563-565]
- /cross_repos/marketing/src/components/radar/RadarOssSignal.tsx[29-35]
## Recommended Fix
Update the marketing repository's OSS privacy and outbound-traffic copy to distinguish default-off, opt-in usage reporting from mandatory traffic and link to the exact report documentation.

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


18. Install guide understates telemetry 🔗 Cross-repo conflict ≡ Correctness
Description
The new usage-data request carries substantially more than Radar's version, operating system, and
architecture. The radar-docs installation guide still says no other telemetry exists, so readers
receive the wrong outbound-data guidance after opting in.
Code

docs/configuration.md[R454-455]

+- **Usage data, only if you opt in**: once a day, to
+  `releases.skyhook.io/radar/usage`. See [Usage data](#usage-data).
Evidence
The PR adds a third outbound request for opt-in usage data, while the published installation page
limits all telemetry to the update check's version and platform fields.

docs/configuration.md[446-455]
docs/configuration.md[477-493]
External repo: skyhook-dev/radar-docs, install.mdx [102]

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 separately maintained installation page says Radar sends no telemetry beyond update-check metadata, which is no longer true for opted-in installations.
## Fix Focus Areas
- docs/configuration.md[454-455]
- /cross_repos/radar-docs/install.mdx[102-102]
## Recommended Fix
Update the radar-docs installation tip to mention the default-off usage report, link to the detailed usage-data documentation, and retain the instructions for blocking outbound traffic.

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


Grey Divider

Tip of the day
💡 Did you know, you can describe a rule in plain language on the Rules page and Qodo drafts it for you

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread internal/server/telemetry_handlers.go Outdated
Comment thread internal/server/telemetry_handlers.go Outdated
Comment thread web/src/api/usage-data.ts
Comment thread web/src/components/settings/PrivacySection.tsx Outdated
Comment thread web/src/components/settings/PrivacySection.tsx Outdated
Comment thread web/src/components/whats-new/WhatsNew.tsx
Comment thread internal/usagedata/usagedata.go
Comment thread internal/usagedata/usagedata.go Outdated
Comment thread docs/configuration.md Outdated
Comment thread docs/configuration.md 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/settings/settings.go
Comment thread internal/usagedata/usagedata.go
@cursor

cursor Bot commented Sep 25, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

1 similar comment
@cursor

cursor Bot commented Sep 26, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@nadaverell
nadaverell force-pushed the claude/radar-telemetry-opt-in-a1517d branch from d224c76 to 19e6256 Compare September 26, 2026 20:44
@nadaverell
nadaverell changed the base branch from claude/whats-new-radar-component-ab1e45 to main September 26, 2026 20:44
@cursor

cursor Bot commented Sep 26, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

2 similar comments
@cursor

cursor Bot commented Sep 26, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@cursor

cursor Bot commented Sep 26, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@nadaverell
nadaverell force-pushed the claude/radar-telemetry-opt-in-a1517d branch from 2483d34 to 68f647e Compare September 27, 2026 09:46
@nadaverell

Copy link
Copy Markdown
Contributor

/review

@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/usagedata/usagedata.go
Comment thread web/src/components/settings/controls.tsx Outdated
Comment thread web/src/App.tsx Outdated
Comment thread internal/usagedata/record.go
Comment thread internal/usagedata/usagedata.go
Comment thread internal/usagedata/usagedata.go
Comment thread internal/server/server.go
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 68f647e

@nadaverell
nadaverell force-pushed the claude/radar-telemetry-opt-in-a1517d branch from 68f647e to 3f40fb5 Compare September 27, 2026 10:00
Adds an optional, anonymous daily usage report, off by default. Nothing is
recorded or sent until the user says yes, and Settings > Privacy shows the
exact report before it is sent.

- A new local install asks once in a corner card; an upgrade asks in What's
  New. "No thanks" is final; an unanswered question may return after three
  months.
- A shared Radar (in a cluster, behind sign-in, or on a reachable listener)
  never asks: the Helm value usageReporting.enabled decides, and Settings
  shows the state read-only. In a cluster the pending report is also sent
  when the pod stops.
- Reports go to usage.radarhq.io with no install or cluster ID and no names:
  view, action, MCP tool, UI event and error counts by fixed vocabulary or
  route pattern, Radar's own setup, and each cluster's minor version,
  platform, node-count range and known integrations.
- Turning it off stops counting, cancels a send in flight and deletes the
  pending ~/.radar/usage-report.json.
- docs/usage-stats.md describes what is sent and what isn't.

Co-authored-by: roylibman <93214016+roylibman@users.noreply.github.com>
@nadaverell
nadaverell force-pushed the claude/radar-telemetry-opt-in-a1517d branch from 3f40fb5 to 78d55a5 Compare September 27, 2026 10:10

@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 78d55a5. Configure here.

Comment thread internal/usagedata/usagedata.go
@nadaverell
nadaverell merged commit 4ad1b64 into main Sep 27, 2026
9 checks passed
@nadaverell
nadaverell deleted the claude/radar-telemetry-opt-in-a1517d branch September 27, 2026 11:36
nadaverell added a commit that referenced this pull request Sep 27, 2026
Adds the v1.15.0 entry to the What's New catalog. This replaces #1877,
which was stacked on the closed #1876; the dialog landed on main in
#1902 instead. `scripts/check-whats-new.sh` (#1908) refuses to release
v1.15.0 without this entry, so this must merge before the tag.

## Highlights
A full-width lead card, then a 2x2 grid. Each secondary card has its own
icon color, so they read apart.

1. **AI investigations that show their work** (lead, no CTA; the
investigations workspace redirects home in run modes that can't host
local agents). This recaps the v1.14.1 rework (#1766, #1802) on purpose:
it's the first What's New dialog, and we want to put AI investigations
in front of people. OpenCode (#1265) is the new v1.15 part.
2. **Full manifest diffs for Argo CD resources** (#1622)
3. **Rightsizing and cost data in MCP**: new agent tools, and risks lead
before savings in the UI too (#1820, #1723)
4. **Deeper batch + AI/ML workloads support**: Kueue, JobSet and Ray
pages; Job and JobSet details show why a queued job has no Pods (#1874,
#1885, #1900, #1849, #1893, #1897)
5. **Canary and blue-green in Topology** (#1626)

## Also in this release
Eight one-liners, paired by length because the grid fills row by row:
table sort memory (#1428), OCI Helm installs (#1815), expiring TLS
certificates in Checks (#1829), Radar Desktop keeping preferences across
restarts (#1907), Pod vs template in Issues (#1848), PVC usage
availability (#1828), Strimzi and admission webhook failures in Issues
(#1840, #1830), `--listen-address` (#1845).

Left out: security fixes, dependency bumps, refactors and CI,
accessibility work (#1901, #1909), and narrow items (ALB action
annotations, INSTALL_DIR). No usage-stats line is needed, because
#1879's consent question already sits in this dialog.

## UI changes beyond the catalog
- The heading names the release line (`v1.15`, via `releaseLine`), since
patch releases show the same notes. The "You just updated from → to"
line keeps exact versions.
- An optional `tone` on highlights colors the icon tile (violet, teal,
emerald, indigo). It uses Radar's existing `bg-X-500/10 text-X-600
dark:text-X-400` pattern, and was checked in light and dark themes.

## Verification
- `scripts/check-whats-new.sh v1.15.0` → found notes
- `make tsc` clean; What's New and usage-data tests: 39 passed
- Previewed live with `VERSION=v1.15.0 make build`, opening
`/?whats-new`, with #1879's consent question in place

The matching public changelog is skyhook-dev/marketing#312.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Release-notes catalog and presentational What's New UI only; no auth,
API, or data-handling changes.
> 
> **Overview**
> Adds the **v1.15.0** What's New catalog entry (five highlights with
CTAs where appropriate, plus eight improvement one-liners) so
`check-whats-new` can ship the tag.
> 
> The dialog heading now shows the **minor release line** via new
`releaseLine()` (e.g. `v1.15` instead of `v1.15.0`), while the upgrade
subtitle still uses exact versions. Secondary highlight cards can set an
optional **`tone`** (violet, teal, emerald, indigo) for distinct icon
tiles; the lead card stays on the accent style.
> 
> Tests cover `releaseLine` and the preview dialog title expectation.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
f0038c3. 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.

2 participants