Skip to content

CLI: Add telemetry - #14304

Merged
sebastianekstrom merged 1 commit into
mainfrom
feature/cli-telemetry
Sep 10, 2026
Merged

sebastianekstrom merged 1 commit into
mainfrom
feature/cli-telemetry

Conversation

@sebastianekstrom

@sebastianekstrom sebastianekstrom commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Add (anonymous) telemetry so we can analyze the usage of the CLI. For each command we now pass:

  • Command
  • Flags
  • Errors
  • Duration
  • OS
  • Agent
  • CI

to PostHog. These can be opted out of with either the POLAR_CLI_TELEMETRY_OPTOUT or DO_NOT_TRACK environment variable.

Screenshot 2026-09-09 at 17 07 43 Screenshot 2026-09-09 at 17 07 49

Fixes https://linear.app/polarsh/issue/PLR-109/add-basic-telemetry

@vercel

vercel Bot commented Sep 9, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
orbit Ready Ready Preview Sep 10, 2026 11:03am UTC
polar-test Ready Ready Preview Sep 10, 2026 11:03am UTC

Request Review

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

OpenAPI Changes

Operations

  • 🔼 POST /v1/cli/telemetry (added)

Schemas

  • 🔼 CLITelemetryEvent (added)

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Generated OpenAPI Client

⚠️ clients/packages/client/src/v1.ts is out of date.

The OpenAPI schema has changed but the generated client has not been regenerated. Please run:

cd clients && pnpm generate

and commit the updated clients/packages/client/src/v1.ts.

Diff stat
diff --git a/clients/packages/client/src/v1.ts b/clients/packages/client/src/v1.ts
index b6f920c..ec9a3a9 100644
--- a/clients/packages/client/src/v1.ts
+++ b/clients/packages/client/src/v1.ts
@@ -2953,6 +2953,23 @@ export interface paths {
     patch?: never
     trace?: never
   }
+  '/v1/cli/telemetry': {
+    parameters: {
+      query?: never
+      header?: never
+      path?: never
+      cookie?: never
+    }
+    get?: never
+    put?: never
+    /** Telemetry */
+    post: operations['cli:telemetry']
+    delete?: never
+    options?: never
+    head?: never
+    patch?: never
+    trace?: never
+  }
   '/v1/files/': {
     parameters: {
       query?: never
@@ -12640,6 +12657,52 @@ export interface components {
        */
       action: 'allow' | 'deny'
     }
+    /** CLITelemetryEvent */
+    CLITelemetryEvent: {
+      /**
+       * Install Id
+       * Format: uuid4
+       * @description Random identifier generated on first run of the CLI.
+       */
+      install_id: string
+      /**
+       * Command
+       * @description Command path, e.g. `auth login`.
+       */
+      command: string
+      /**
+       * Flags
+       * @description Flag names, never values.
+       */
+      flags?: string[]
+      /**
+       * Outcome
+       * @enum {string}
+       */
+      outcome: 'success' | 'failure' | 'interrupted'
+      /** Error */
+      error?: string | null
+      /** Duration Ms */
+      duration_ms: number
+      /** Cli Version */
+      cli_version: string
+      /** Os */
+      os: string
+      /** Arch */
+      arch: string
+      /** Runtime */
+      runtime: string
+      /**
+       * Agent
+       * @description Detected AI coding agent, if any.
+       */
+      agent?: string | null
+      /**
+       * Ci
+       * @default false
+       */
+      ci: boolean
+    }
     /** CannotCreateOrganizationError */
     CannotCreateOrganizationError: {
       /**
@@ -47549,6 +47612,39 @@ export interface operations {
       }
     }
   }
+  'cli:telemetry': {
+    parameters: {
+      query?: never
+      header?: never
+      path?: never
+      cookie?: never
+    }
+    requestBody: {
+      content: {
+        'application/json': components['schemas']['CLITelemetryEvent']
+      }
+    }
+    responses: {
+      /** @description Successful Response */
+      202: {
+        headers: {
+          [name: string]: unknown
+        }
+        content: {
+          'application/json': unknown
+        }
+      }
+      /** @description Validation Error */
+      422: {
+        headers: {
+          [name: string]: unknown
+        }
+        content: {
+          'application/json': components['schemas']['HTTPValidationError']
+        }
+      }
+    }
+  }
   'files:list': {
     parameters: {
       query?: {
@@ -68669,6 +68765,9 @@ export const billingAddressFieldModeValues: ReadonlyArray<
 export const body_oauth2_consentActionValues: ReadonlyArray<
   FlattenedDeepRequired<components>['schemas']['Body_oauth2_consent']['action']
 > = ['allow', 'deny']
+export const cLITelemetryEventOutcomeValues: ReadonlyArray<
+  FlattenedDeepRequired<components>['schemas']['CLITelemetryEvent']['outcome']
+> = ['success', 'failure', 'interrupted']
 export const checkoutCreatedEventNameValues: ReadonlyArray<
   FlattenedDeepRequired<components>['schemas']['CheckoutCreatedEvent']['name']
 > = ['checkout.created']

@frankie567 frankie567 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Do we really need to proxy this via our server? Couldn't we directly hit Posthog? Their client has also lot of goodness like batching and such: https://posthog.com/docs/libraries/node#installation

@sebastianekstrom

Copy link
Copy Markdown
Contributor Author

@frankie567 yes sorry I was supposed to set this PR as a draft, that is the plan!

Copy link
Copy Markdown
Member

Awesome 🙂

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1 issue found across 7 files

Confidence score: 3/5

  • In server/polar/cli/schemas.py, unauthenticated telemetry accepts up to 25 arbitrarily large flag strings and forwards them to PostHog, creating an avoidable resource or payload-abuse risk; cap each flag string’s length before forwarding.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="server/polar/cli/schemas.py">

<violation number="1" location="server/polar/cli/schemas.py:13">
P2: An unauthenticated telemetry request can include 25 arbitrarily large flag strings because `max_length=25` limits only the item count, then forwards them to PostHog. Bound each flag string before forwarding.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread server/polar/cli/schemas.py Outdated
description="Random identifier generated on first run of the CLI."
)
command: str = Field(max_length=100, description="Command path, e.g. `auth login`.")
flags: list[str] = Field(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: An unauthenticated telemetry request can include 25 arbitrarily large flag strings because max_length=25 limits only the item count, then forwards them to PostHog. Bound each flag string before forwarding.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/polar/cli/schemas.py, line 13:

<comment>An unauthenticated telemetry request can include 25 arbitrarily large flag strings because `max_length=25` limits only the item count, then forwards them to PostHog. Bound each flag string before forwarding.</comment>

<file context>
@@ -0,0 +1,26 @@
+        description="Random identifier generated on first run of the CLI."
+    )
+    command: str = Field(max_length=100, description="Command path, e.g. `auth login`.")
+    flags: list[str] = Field(
+        default_factory=list, max_length=25, description="Flag names, never values."
+    )
</file context>

@sebastianekstrom

sebastianekstrom commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor Author

@cubic-dev-ai review

@sebastianekstrom

Copy link
Copy Markdown
Contributor Author

@cubic-dev-ai re review

@cubic-dev-ai

cubic-dev-ai Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai re review

@sebastianekstrom I have started the AI code review. It will take a few minutes to complete.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1 issue found across 4 files

Confidence score: 4/5

  • clients/packages/cli/src/services/telemetry.ts: An unreachable or slow PostHog service can add up to 1.5 seconds to every release command via Effect.onExit, creating a noticeable CLI slowdown; move telemetry fire-and-forget outside the command exit path or otherwise avoid blocking shutdown.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="clients/packages/cli/src/services/telemetry.ts">

<violation number="1" location="clients/packages/cli/src/services/telemetry.ts:234">
P2: When PostHog is unreachable or slow, every release command waits up to 1.5 seconds for telemetry during `Effect.onExit` before the process exits. Run telemetry fire-and-forget outside the command's exit path, or otherwise avoid making command completion depend on the network timeout.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread clients/packages/cli/src/services/telemetry.ts Outdated
yield* client.execute(request)
}).pipe(
Effect.scoped,
Effect.timeout(SEND_TIMEOUT),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2: When PostHog is unreachable or slow, every release command waits up to 1.5 seconds for telemetry during Effect.onExit before the process exits. Run telemetry fire-and-forget outside the command's exit path, or otherwise avoid making command completion depend on the network timeout.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At clients/packages/cli/src/services/telemetry.ts, line 234:

<comment>When PostHog is unreachable or slow, every release command waits up to 1.5 seconds for telemetry during `Effect.onExit` before the process exits. Run telemetry fire-and-forget outside the command's exit path, or otherwise avoid making command completion depend on the network timeout.</comment>

<file context>
@@ -0,0 +1,237 @@
+    yield* client.execute(request)
+  }).pipe(
+    Effect.scoped,
+    Effect.timeout(SEND_TIMEOUT),
+    Effect.provide(http),
+    Effect.ignore,
</file context>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@sebastianekstrom This one might makes sense from a UX perspective. It would be pretty weird if I tried to exit a command and it waited 1.5 seconds. I know it's not going to be very rare though, let me know what you think.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I checked and stripe is doing the same so maybe I overreacted

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed though, and fixed!

['gemini-cli', /^GEMINI_CLI$/],
['copilot', /^(GITHUB_)?COPILOT_/],
['aider', /^AIDER_/],
['windsurf', /^WINDSURF_/],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Does windsurf even exist? 😅

I'd add opencode and pi to this list, I'm pretty sure they have more users than windsurf aider, probably even gemini-cli and copilot

Comment on lines +190 to +192
env = process.env,
home = homedir(),
http = FetchHttpClient.layer,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This might sound nitpick-y but this is not very idiomatic effect to have these as parameters and the agent probably just did it for testing purposes. Ideally we would have a telemetry service with a record method and the implementation of that service would depend on the FileSystem service and Http service from effect, then in tests a mock implementation of those services can be provided.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good point, fixed!

@sebastianekstrom
sebastianekstrom added this pull request to the merge queue Sep 10, 2026
Merged via the queue into main with commit 58c39b7 Sep 10, 2026
26 of 27 checks passed
@sebastianekstrom
sebastianekstrom deleted the feature/cli-telemetry branch September 10, 2026 11:51

This branch was successfully deployed

2 active deployments
Preview – polar-test — effed8a8 Deployed Sep 10, 2026 by vercel[bot]
Preview – orbit — effed8a8 Deployed Sep 10, 2026 by vercel[bot]
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.

3 participants