From 6dbcb0a8248fd591028f88523a2ba73b9b3f0a96 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Sun, 9 Aug 2026 14:43:12 -0700 Subject: [PATCH] Stamp PostHog app version on every telemetry event PostHog Version breakdown reads the standard app version property. Events only sent a custom service_version, so every release bucketed as Other. Dual-stamp the package version so the built-in filter works and existing dashboards keep working. --- docs/TELEMETRY.md | 4 +++- src/telemetry/index.ts | 8 ++++++-- tests/unit/telemetry.test.ts | 3 +++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/docs/TELEMETRY.md b/docs/TELEMETRY.md index 61726a2b5..00b133faa 100644 --- a/docs/TELEMETRY.md +++ b/docs/TELEMETRY.md @@ -33,7 +33,9 @@ there is nothing to compact — an event that also fires on no-ops makes its own duration and turn-count averages meaningless. Common properties attached to every event: a random installation UUID -(`distinct_id`), `session_id`, `service_version`, `os_type`, `os_arch`, and a +(`distinct_id`), `session_id`, `$app_version` (PostHog's standard Version +property, the running package version), `service_version` (same value, kept +for existing custom-property dashboards), `os_type`, `os_arch`, and a `schema_version` for forward compatibility. Approximate country-level location is derived server-side by PostHog from the diff --git a/src/telemetry/index.ts b/src/telemetry/index.ts index 70e60822c..85d0379ca 100644 --- a/src/telemetry/index.ts +++ b/src/telemetry/index.ts @@ -91,8 +91,8 @@ export function getSessionId(): string { // Per-event property allowlist. Anything not listed here is stripped before // the payload leaves the process. Together with the fixed common properties -// capture() appends (service_version, os_type, os_arch, schema_version, -// session_id), this bounds everything telemetry can ever contain. +// capture() appends ($app_version, service_version, os_type, os_arch, +// schema_version, session_id), this bounds everything telemetry can ever contain. const EVENT_PROPERTY_ALLOWLIST: Record = { cli_start: [], session_end: ["status", "turn_count", "duration_ms", "session_mode", "exit_reason"], @@ -353,6 +353,10 @@ export function createTelemetry(options: CreateTelemetryOptions): Telemetry { timestamp: new Date().toISOString(), properties: { ...allowedProperties(event, properties), + // PostHog's built-in Version breakdown reads $app_version; without it + // every event buckets as "Other". service_version is the same value + // kept for dashboards that already filter on the custom property. + $app_version: pkg.version, service_version: pkg.version, os_type: process.platform, os_arch: process.arch, diff --git a/tests/unit/telemetry.test.ts b/tests/unit/telemetry.test.ts index b815f30b2..1d7c621d9 100644 --- a/tests/unit/telemetry.test.ts +++ b/tests/unit/telemetry.test.ts @@ -259,6 +259,9 @@ test("capture payload shape includes distinct_id and common props, with no clien expect(body.properties.$geoip_disable).toBeUndefined(); expect(body.properties.schema_version).toBe(1); expect(typeof body.properties.service_version).toBe("string"); + expect(body.properties.$app_version).toBe(body.properties.service_version); + expect(typeof body.properties.$app_version).toBe("string"); + expect(String(body.properties.$app_version).length).toBeGreaterThan(0); expect(body.properties.os_type).toBe(process.platform); expect(body.properties.os_arch).toBe(process.arch); });