Skip to content

feat(auth): identify accounts and add structured login telemetry#212

Merged
miguel-heygen merged 7 commits into
mainfrom
feat/conversion-telemetry
Jul 11, 2026
Merged

feat(auth): identify accounts and add structured login telemetry#212
miguel-heygen merged 7 commits into
mainfrom
feat/conversion-telemetry

Conversation

@miguel-heygen

Copy link
Copy Markdown
Contributor

Summary

heygen-cli's own analytics reported to a separate PostHog project under a locally-minted identity, with no identify call on login — so a successful heygen auth login couldn't be joined to anything the rest of the toolchain already tracks, and login attempts only ever showed up as a generic command-run event with no method or failure reason. This wires heygen-cli into the same cross-tool identity and destination, and makes login itself first-class, queryable telemetry.

Companion change to a matching PR in heygen-com/hyperframes: heygen-com/hyperframes#2130 (media-use side: failure classification + free/paid tagging + test-isolation hardening). The two land independently — neither repo's release blocks the other.

What changed

  • Shared install identity: the analytics client's distinct id now comes from the same cross-tool install identity file other HeyGen CLIs already share, promoting any existing local id on first run so upgrading users don't reset their history.
  • Shared destination + surface tag: events now land in the same ingestion project as the rest of the toolchain, tagged with a surface property so a query can still isolate this CLI's traffic.
  • Account identify on login: a successful login (OAuth or API-key) now links the anonymous install identity to the signed-in account, the same way the rest of the toolchain already does — plus a one-time console notice disclosing this.
  • Structured login telemetry: auth login attempts now emit distinct started/completed/failed events carrying the method (OAuth vs. API-key) and, on failure, a specific reason drawn from the login flow's real failure branches (timeout, cancellation, invalid input, etc.) instead of a generic pass/fail.
  • Reconciliation fix: a handful of rare early-return error paths (malformed credentials, save/clear failures, PKCE/state generation, loopback startup) previously left a started event with no matching completed/failed; every path now reconciles through a safety-net terminal event.

Design decisions

  • Identity adoption is a read-merge-write against the shared identity file, never a blind overwrite — the file is written by more than one tool, so a write here must not drop a field another tool wrote moments earlier.
  • The anon-to-identified merge uses this repo's pinned analytics SDK version's actual merge primitive, verified against that SDK's source rather than assumed from a newer version's API.
  • Every new analytics call preserves the existing fire-and-forget contract (guarded, error-swallowing, non-blocking) — none of this can affect the login flow's own success/failure behavior.

Testing

  • Reviewed end-to-end (correctness, no auth-behavior change, fire-and-forget discipline, identity file handling, SDK usage, test coverage) with no blocking issues found.
  • Go toolchain unavailable in this environment for a live go test run; verified by careful manual reading and cross-checking against the pinned SDK's actual source.

Post-Deploy Monitoring & Validation

  • No additional operational monitoring required beyond normal telemetry review — this is additive, best-effort instrumentation with no change to auth flow behavior, credential storage, or login outcomes.
  • Expected healthy signal: no change in login success/failure rates or latency (every new call is fire-and-forget and swallows its own errors).

…nd structured login events

Switch the analytics client's distinct id to the same cross-tool install
identity already shared by the other HeyGen CLIs (promoting any existing
local id on first run so upgraders keep their identity), and point events
at the same ingestion project so a person's activity is queryable in one
place instead of two. Add a `surface` property to distinguish this CLI's
traffic there, plus IdentifyAccount/AuthLoginStarted/AuthLoginCompleted/
AuthLoginFailed methods so a login attempt becomes structured, queryable
telemetry (method + specific failure reason) instead of a generic event.
Link a successful login (OAuth or API-key) to the account identity via
PostHog identify, disclose telemetry + identity linking on first run, and
emit structured started/completed/failed events per login attempt with
method and failure reason so login funnels are queryable by branch instead
of a generic command-complete event.
…vent

runAPIKeyLogin and runOAuthLogin had several early-return error branches
(malformed credentials file, save/clear failures, PKCE/state generation,
loopback startup, authorize-URL build, response encoding) that returned
without emitting AuthLoginCompleted or AuthLoginFailed, leaving their
AuthLoginStarted event dangling with no outcome.

Rather than instrumenting each scattered return site, wrap both runners
with a deferred catch-all: a per-call `reported` flag is set at each
existing AuthLoginCompleted/AuthLoginFailed call, and a defer fires
AuthLoginFailed(method, "internal_error") only when the function returns
an error and nothing else already reported an outcome. New error paths
added later are covered automatically instead of silently reintroducing
the gap.
@miguel-heygen miguel-heygen force-pushed the feat/conversion-telemetry branch from 1d0a531 to 857dbd3 Compare July 10, 2026 18:06
runOAuthLogin and runAPIKeyLogin marked the login as completed and fired
AuthLoginCompleted before writing the success response via
ctx.formatter.Data. If that write failed (e.g. a broken pipe on stdout),
the command still exited non-zero but telemetry had already recorded a
completed login. Reorder so completion is only reported once the
response write actually succeeds; a write failure now falls through to
the existing deferred reconciliation, which reports AuthLoginFailed
instead.

@somanshreddy somanshreddy left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed the full diff (all 8 files), traced the auth + telemetry flow end-to-end, and cross-checked against the hyperframes scheme + posthog-go v1.11.2. Code LGTM — no correctness blockers. Fire-and-forget contract holds, login behavior unchanged, and no secret/token/PII in event properties (AUTH_LOGIN_* carry only method + a fixed reason enum + base props). The email-as-distinct_id is the intended, disclosed cross-tool identity.

✅ Update (re-review 2026-07-10): email-case fix landed and is symmetric. identityKey now strings.ToLower(...) on both email and username (test: Demo@Example.com), and the hyperframes side lowercases too — so the cross-tool PostHog join holds without case-split profiles. This was the one substantive follow-up; it's resolved.

On the red govulncheck check — not introduced by this PR. #212 touches none of update.go, go.mod, go.sum, or the Go toolchain. Both findings trace entirely through untouched code:

  • GO-2026-5932golang.org/x/crypto/openpgp (unmaintained) via the self-update path (cmd/heygen/update.go). Fixed-in: N/A → needs migration off openpgp or an accepted-risk suppression, as a separate item.
  • GO-2026-5856crypto/tls ECH advisory in the stdlib, fixed in go1.25.12 (CI runs 1.25.11) → toolchain bump clears it.

These are recent 2026 advisories the live vuln DB just began flagging repo-wide; they'd fail on any branch right now. Recommend a separate housekeeping ticket rather than scope-creeping this telemetry PR — and confirm whether govulncheck is a required merge gate.

Non-blocking nits:

  1. internal/analytics/analytics.goIdentifyAccount mutates c.identified without synchronization. No real race today (single-goroutine login); a one-line comment on the assumption would harden intent.
  2. main.go:98-103 — per-tool telemetry notice: a user who saw it via media-use sees it again from heygen-cli. Product decision, flagging so it's conscious.
  3. auth_login.go device-code path fires STARTED + FAILED for the unsupported flag each invocation — fine, just confirm you want device-code attempts in the funnel.

— Somu

…join

An uppercase-cased email or username would otherwise split one HeyGen
account across two PostHog profiles, since hyperframes-oss's side of the
identity join already lowercases.
os.UserHomeDir reads USERPROFILE on Windows, not HOME, so these tests
leaked the real CI runner's home directory on windows-latest.
@jrusso1020

Copy link
Copy Markdown
Collaborator

E2E verification (complement to Somu/tai's code passes; deferring approval to Terence/Somansh per the cross-tool-telemetry policy).

Ran c524687d in a throwaway worktree (go build/go test + real emit-path capture, posthog-go v1.11.2 → local capture, nothing sent to prod PostHog):

  • Builds + tests greeninternal/analytics + cmd/heygen ok; regressions pass: TestRunOAuthLogin_IdentifyAccount_EmailLowercased, TestRunAuthLogin_APIKey_IdentifyAccount, TestIdentifyAccount_EnqueuesAliasThenIdentify, TestDistinctID_UsesSharedConfigAnonymousId, TestBaseProperties_SuppressesIP, both formatter-failure telemetry tests.
  • Identity join → one profile (verified): distinctID read from the shared ~/.hyperframes/config.json = shared-anon-e2e-0001 (same file/value HF reads); login emitted $create_alias distinct_id=demo@example.com alias=shared-anon-e2e-0001 + $identify distinct_id=demo@example.com → merges the same anon into the same lowercased-email account as hyperframes #2130. Symmetric stitch, one person.
  • No gaps: every AUTH_LOGIN_STARTED reconciles to COMPLETED or FAILED; the formatter-failure safety-net (named-return+defer) emits AuthLoginFailed(internal_error). Only started-less path is picker-cancel (deliberate); probe-failure → completes without identify (fails consistently with HF, doesn't split).
  • PII-clean from captured payloads: {command,method,surface,cli_version,os,arch,client_origin,$ip:null} — no token/key/PKCE-verifier/raw error string; method/reason fixed enums.
  • CI: green on all required contexts (test ubuntu/macos/windows, lint, secrets, goreleaser-check). Only red is govulncheck (not required): GO-2026-5932 (x/crypto/openpgp via update.go) + GO-2026-5856 (crypto/tls stdlib, fixed go1.25.12) — both pre-existing, untouched by this 8-file diff; main is red on the same job.
  • Couldn't run real browser OAuth headlessly — used the api_key path + the httptest-IdP OAuth telemetry tests (oauth vs api_key differ only in the method string).

Merge-ready on the e2e dimension.

— Rames Jusso

@jrusso1020

Copy link
Copy Markdown
Collaborator

Live person-merge — now confirmed (closes the "didn't push to prod PostHog" caveat in my earlier e2e comment).

Authenticated the test account via real OAuth (PKCE loopback, headless Chrome) on CLI c524687d, ran both tools signed in, and emitted real events to prod PostHog (project 356858). Server-side HogQL:

distinct_persons = 1
distinct_ids     = 2   → [account-email, shared-anon]

Both ids resolve to one person 367dddc0-… — the account's pre-existing canonical person, with the fresh shared anon (from ~/.hyperframes/config.json) merged into it (right direction, no orphaned anon). Every real event from both tools — CLI AUTH_LOGIN_STARTED/COMPLETED(method=oauth) + $create_alias + $identify, and media-use $identify / media_use_resolve / media_use_provider_error — lands on that one person_id. The cross-tool join is now proven end-to-end against live PostHog; the uppercase-split/double-count failure mode does not occur.

Only remaining unproven-live detail: auth_method=oauth on a successful heygen media_use_resolve — blocked by an outdated local heygen binary (v0.1.1, rejects --headers), not by either PR; stays code+test-verified. Merge-ready on the e2e dimension.

— Rames Jusso

Switching heygen-cli's write-key to hyperframes-oss's shared project
would silently flatline heygen-cli's own existing dashboard the moment a
client upgrades, since no dashboard/query migration has happened yet.
Fan every event out to both destinations so that dashboard keeps
receiving fresh data until it's migrated to query the shared project.
@miguel-heygen

Copy link
Copy Markdown
Contributor Author

Addressed the dashboard-continuity concern raised in review: this now dual-writes every event to both PostHog destinations instead of a hard cutover, so the existing heygen-cli dashboard keeps receiving fresh data rather than going dark as clients upgrade. New tests cover the fan-out and its failure isolation (one destination erroring doesn't block the other).

@somanshreddy somanshreddy left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approved.

@miguel-heygen miguel-heygen merged commit 257819c into main Jul 11, 2026
8 of 9 checks passed
@miguel-heygen miguel-heygen deleted the feat/conversion-telemetry branch July 11, 2026 00:07
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