Skip to content

feat: add PostHog event tracking for CLI installs and upgrades (ENG-2277) - #293

Open
devin-ai-integration[bot] wants to merge 8 commits into
mainfrom
thomas-devin/track-cli-sdk-installs-posthog
Open

feat: add PostHog event tracking for CLI installs and upgrades (ENG-2277)#293
devin-ai-integration[bot] wants to merge 8 commits into
mainfrom
thomas-devin/track-cli-sdk-installs-posthog

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented May 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds lightweight PostHog event tracking to the CLI with no new dependencies — uses only net/http and crypto/rand from stdlib. Mirrors the existing Sentry integration pattern (build-time key injection, opt-in consent via IsTrackingEnabled()).

Two events are tracked:

  • "Installed CLI" — fires once per new CLI version on startup, deduplicated via ~/.blaxel/telemetry.json
  • "Upgraded CLI" — fires after a successful bl upgrade with old_version and new_version properties

Events are fire-and-forget (async goroutine, 5s timeout). The PostHog API key is injected at build time via ldflags, same as sentryDSN.

Files changed:

  • cli/core/posthog.go (new) — PostHog client, telemetry state persistence, UUID generation, deduplication logic
  • main.go — adds posthogKey ldflag var, initializes PostHog when tracking is enabled
  • cli/core/root.go — calls TrackCLIInstalled(version) in Execute()
  • cli/upgrade.go — captures old version, detects new version post-upgrade, fires TrackCLIUpgraded
  • .goreleaser.yaml / Makefile / .github/workflows/release.yaml — build-time key injection

Review & Testing Checklist for Human

  • POSTHOG_KEY GitHub secret must be created before the next release. Verify that GoReleaser doesn't fail when POSTHOG_KEY env var is unset/empty (it may need to be set to "" like SENTRY_DSN).
  • detectInstalledVersion() output parsing — the version command uses core.Print/core.PrintInfo which may emit ANSI color codes. Verify that parsing "Version: X.Y.Z" works correctly when the new binary's stdout is not a TTY (piped through exec.Command(...).Output()).
  • FlushPosthog() uses time.Sleep(500ms) instead of a sync.WaitGroup — events could be silently dropped if the HTTP POST takes longer. Acceptable trade-off? Consider if a WaitGroup approach would be better.
  • loadTelemetryState uses sync.Once — telemetry state is loaded once per process. This is fine for normal CLI usage but means the state won't be re-read within a single process lifetime (relevant for upgrade flow where TrackCLIUpgraded then updates state).
  • Test the full flow locally: build with POSTHOG_KEY=phc_test make build-dev, run bl version twice, verify ~/.blaxel/telemetry.json is created with correct structure, and that only one "Installed CLI" event would fire.

Notes

  • No unit tests added for posthog.go — the code is intentionally minimal and mirrors the untested Sentry client pattern in this repo.
  • The getDistinctID() comment mentions "workspace email" but the implementation only uses a persisted anonymous UUID. The comment should be updated or the email lookup added if that's desired.
  • The SDKs field in telemetryState is forward-looking — it's unused by the CLI but will be used by the SDK repos (sharing the same ~/.blaxel/telemetry.json file).

ENG-2277

Link to Devin session: https://app.devin.ai/sessions/43a6073c1fe54dbfb87b42e3dc01db56
Requested by: @Grotoma


Open in Devin Review

Note

Adds a 7afd2e6 commit that makes CLI install/upgrade telemetry reliable: events are only persisted to telemetry.json after PostHog confirms delivery (2xx response), in-flight events are deduplicated via a pendingCLIEvents map with proper mutex protection, and the brew upgrade path now uses brew --prefix blaxel instead of exec.LookPath to avoid executing unrelated PATH binaries. Tests cover deduplication, failed delivery retry, and PATH-binary safety.

Written by Mendral for commit 7afd2e6.

- Add lightweight PostHog client (cli/core/posthog.go) using raw net/http
- Track 'Installed CLI' event on first run of each new version
- Track 'Upgraded CLI' event with old/new versions after successful upgrade
- Deduplicate events via ~/.blaxel/telemetry.json
- Inject PostHog API key at build time via ldflags
- Fire-and-forget async HTTP POST, non-blocking

ENG-2277

Co-Authored-By: tcrochet <tcrochet@blaxel.ai>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

mendral-app[bot]

This comment was marked as outdated.

…detection, comment fix

- Use conditional template in .goreleaser.yaml so POSTHOG_KEY env var is optional
- Clarify detectInstalledVersion re-resolves symlinks for brew upgrades
- Fix misleading comment on getDistinctID (UUID only, no email)

Co-Authored-By: tcrochet <tcrochet@blaxel.ai>
mendral-app[bot]

This comment was marked as outdated.

On macOS, os.Executable() returns the already-resolved cellar path,
so EvalSymlinks cannot follow the updated symlink after brew upgrade.
Using exec.LookPath finds the binary by name in PATH, which resolves
through the updated /usr/local/bin symlink to the new cellar entry.

Co-Authored-By: tcrochet <tcrochet@blaxel.ai>
mendral-app[bot]

This comment was marked as outdated.

devin-ai-integration[bot]

This comment was marked as resolved.

…erve unknown telemetry fields

Co-Authored-By: tcrochet <tcrochet@blaxel.ai>
mendral-app[bot]

This comment was marked as outdated.

…ss on os.Exit

Co-Authored-By: tcrochet <tcrochet@blaxel.ai>
mendral-app[bot]

This comment was marked as outdated.

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

❌ Cannot revive Devin session - the session is too old. Please start a new session instead.

mendral-app[bot]

This comment was marked as outdated.

@mendral-app

mendral-app Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

🧪 Testing Guide

What this PR addresses

Adds lightweight PostHog event tracking to the CLI to monitor install and upgrade events. Two events are tracked:

  • "Installed CLI" — fires once per new CLI version on startup, deduplicated via ~/.blaxel/telemetry.json
  • "Upgraded CLI" — fires after a successful bl upgrade with old/new version properties

Uses only stdlib (net/http, crypto/rand), no new dependencies. Mirrors the existing Sentry integration pattern with build-time key injection and opt-in consent.

Steps to verify the new behavior

1. Unit tests pass

go test ./cli/core/ -run TestPosthog -v
go test ./cli/ -run TestUpgrade -v

2. Install tracking (manual)

  1. Build the CLI locally: make build (or with a dummy POSTHOG_KEY)
  2. Delete ~/.blaxel/telemetry.json if it exists
  3. Ensure tracking is enabled (check IsTrackingEnabled() returns true — may require setting appropriate env/config)
  4. Run any bl command (e.g., bl --version)
  5. Verify ~/.blaxel/telemetry.json is created and contains the current version
  6. Run bl again — confirm no duplicate event is sent (same version already recorded)

3. Upgrade tracking (manual)

  1. Build a CLI binary with a known version string
  2. Run bl upgrade successfully (or simulate the upgrade flow)
  3. Confirm TrackCLIUpgraded() is called with correct old_version / new_version properties
  4. Verify detectInstalledVersion() correctly runs the upgraded binary with BL_SKIP_TELEMETRY=1 to avoid nested telemetry

4. Opt-out respected

  1. Disable tracking (set the appropriate env variable or config so IsTrackingEnabled() returns false)
  2. Run any bl command
  3. Confirm no HTTP requests are made to PostHog and no telemetry file is written/updated

5. Build configuration

  • Verify .goreleaser.yaml correctly injects the PostHog key via ldflags
  • Verify the GitHub Actions release workflow has access to POSTHOG_KEY secret

What to verify (expected behavior)

  • All new and existing tests pass (go test ./...)
  • No new external dependencies added (only stdlib usage)
  • Events are fire-and-forget (async goroutine, 5s timeout) — CLI startup latency is not affected
  • FlushPosthog() is called on all exit paths (normal exit, ExitWithError, ExitWithMessage)
  • The anonymous user ID is persistent across sessions (stored in ~/.blaxel/telemetry.json)
  • Install event is deduplicated — only fires once per CLI version
  • Upgrade detection uses BL_SKIP_TELEMETRY=1 to prevent recursive telemetry when probing the new binary's version
  • No sensitive data is included in events (only version strings and anonymous ID)

Note

Posted by PR Testing Guide · Tag @mendral-app with feedback.

@chirag03k
chirag03k requested a deployment to integration-tests July 22, 2026 00:18 — with GitHub Actions Waiting
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