Skip to content

feat(cli): associate signed-in HeyGen account with telemetry - #2020

Merged
miguel-heygen merged 2 commits into
mainfrom
worktree-cli-auth-identify
Jul 7, 2026
Merged

feat(cli): associate signed-in HeyGen account with telemetry#2020
miguel-heygen merged 2 commits into
mainfrom
worktree-cli-auth-identify

Conversation

@miguel-heygen

Copy link
Copy Markdown
Collaborator

What

Follow-up to the sign-in telemetry (#2000). That change counts the sign-in funnel but attributes everything to the anonymous install id, so a completed sign-in can't be tied to the account it produced. This associates the two.

  • On a completed sign-in, emit a PostHog $identify alias whose $anon_distinct_id is the install's anonymousId (so events recorded before sign-in stitch to the same person), and tag auth_login_completed with the account identity via the distinctId arg that was pre-plumbed for this.
  • /v3/users/me returns no opaque user_id, so the identity key is the account email, falling back to username (single identityKey helper; flip the order there to prefer the less-identifying username).
  • Wired into both the OAuth and --api-key paths. Completion now fires after the identity probe so it can carry the identity; if the probe fails transiently the sign-in still records as completed, just without an identity.

Privacy

This is the first PII the CLI attaches, so the disclosure is updated in the same PR:

  • the first-run telemetry notice (showTelemetryNotice), and
  • the telemetry section of docs/packages/cli.mdx

both now state that signing in links your account email to your usage. Everything still no-ops under hyperframes telemetry disable / HYPERFRAMES_NO_TELEMETRY=1, and identification only happens after the user chooses to sign in.

Tests & verification

  • Unit: identifyUser payload + no-op on empty; completion attribution including the username fallback and the no-identity cases (transient probe failure, rejected key). 34 tests green, typecheck + lint + fallow clean.
  • End-to-end against the built CLI (telemetry only fires from a build, not from source): confirmed the emitted sequence is cli_command (anon) → auth_login_started (anon) → $identify (distinct_id = email, $anon_distinct_id = install id) → auth_login_completed (distinct_id = email). The live /v3/users/me call succeeded; the PostHog POST was intercepted so nothing hit production analytics.

Note: no data lands until a CLI release ships with this.

Sign-in telemetry currently attributes everything to the anonymous
install id, so the sign-in funnel can be counted but a completed sign-in
can't be tied to the account it produced. This associates the two.

- On a completed sign-in, emit a PostHog `$identify` alias whose
  `$anon_distinct_id` is the install's anonymousId, so events recorded
  before sign-in stitch to the same person, and tag `auth_login_completed`
  with the account identity (the pre-plumbed `distinctId`).
- `/v3/users/me` exposes no opaque user_id, so the identity key is the
  account email, falling back to username (single `identityKey` helper).
- Both no-op under the `telemetry disable` opt-out and only fire after
  the user chooses to sign in.

Privacy disclosure updated in lockstep, since this is the first PII the
CLI attaches: the first-run telemetry notice and the telemetry section
of docs/packages/cli.mdx now state that signing in links your account
email to your usage.

Tests: identifyUser payload + no-op, completion attribution incl.
username fallback and no-identity-on-reject/empty. Verified end-to-end
against the built CLI: pre-auth events anonymous, $identify carries
$anon_distinct_id, completion carries the account email.
@mintlify

mintlify Bot commented Jul 7, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
hyperframes 🟢 Ready View Preview Jul 7, 2026, 6:02 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

Review gating item: identityKey is `email ?? username`, but the
first-run notice and cli.mdx said only "email", so an emailless
account's username would reach PostHog undisclosed. `/v3/users/me`
treats email as optional (pickString), so the fallback is live code,
not dead — disclose it rather than assert an unverifiable email
guarantee. Both surfaces now say "email, or username if the account
has no email".

Also soften the identityKey comment: it implied username is "less
identifying", but HeyGen usernames are often email-shaped, so the note
now states username is a fallback, not a privacy win.
@miguel-heygen

Copy link
Copy Markdown
Collaborator Author

Thanks both. Addressed in cd02297.

Gating — email-vs-username disclosure gap (tai #1 / Rames): disclosed the fallback rather than dropping it. /v3/users/me treats email as optional (pickString returns undefined if absent), and I can't prove the response always carries an email, so ?? username is live code, not dead — dropping it would silently unattribute an emailless account and assert a guarantee I can't back. Both surfaces now read "your account email (or username, if your account has no email)":

  • first-run notice (showTelemetryNotice)
  • docs/packages/cli.mdx

Nit — "less identifying" comment (tai): softened. The identityKey comment now states username is a fallback for emailless accounts, explicitly not a privacy win (usernames are often email-shaped), so a future maintainer isn't nudged into a false-privacy refactor.

Non-gating, not doing now (with reasons):

  • No-op-when-disabled pin-test: skipped. identifyUser routes through trackEvent, so it inherits the exact gate every other event uses — there's no identify-specific bypass to guard. A meaningful gate test also has to defeat isDevMode() (which short-circuits shouldTrack to false whenever the running module path ends in .ts, i.e. always in the source test context), so a naive "disabled → no fetch" test passes trivially without proving the env gate. Net: it'd regression-guard pre-existing client gating, not this change. Happy to add it as a client.test.ts if you'd rather have the belt-and-suspenders.
  • Forget-me / historical alias: logging as a future item, not building — a deletion/unlink flow is its own scoped piece and the current behavior (disable stops future events; PostHog retains the historical install→identity alias) is industry-standard and now disclosed.

Waiting on green.

@miguel-heygen
miguel-heygen merged commit 3d8372f into main Jul 7, 2026
42 checks passed
@miguel-heygen
miguel-heygen deleted the worktree-cli-auth-identify branch July 7, 2026 06:23
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.

1 participant