fix(telemetry): never identify the anonymous installation_id (restore login stitch)#1114
Conversation
… login stitch) PostHog marks any id passed to identify() as an identified person and refuses to merge one identified id into another. Calling identify() with the anonymous installation_id at boot (and on logout, and on every anonymous person-property write) burned it, so the login-time alias(installation_id -> user_id) was a silent no-op: 0 of 13,528 device ids ever stitched onto an account. Invariant: client.identify() is only ever called with the authenticated Firebase user_id, in bindUserId. All anonymous person-property writes ($set / $set_once) now route through a capture-$set on a dedicated comfy.desktop.person.set event, which updates the person without emitting $identify. Fixes the boot bind, unbindUserId (logout), and both registerPersonProperties paths.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughTelemetry person-property writes are refactored to avoid calling ChangesCapture-set refactor for anonymous person properties
Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 2✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install timed out. The project may have too many dependencies for the sandbox. Comment |
Trim the verbose docstrings added with the stitch fix to the load-bearing points, and correct a header reference to a non-existent setAnonymousDistinctId() (the function is identify()).
The bug
Anonymous desktop activity never stitches onto the account at login: 0 of 13,528 device ids ever stitched (per James's PostHog audit). On sign-in, the user keeps a fresh identity and all pre-login device history (sessions, installs, first-use, generations) is orphaned under the anonymous
installation_id.Root cause
PostHog marks any id passed to
identify()as an identified person, and it will not merge one identified id into another. We were callingclient.identify()with the anonymousinstallation_idin four places, which "burned" it:index.ts):identify(installationId, {app_version, platform, arch, id_class})fired a$identifyon the anon id on every consented launch, before any login.unbindUserId): re-identify()d the anon id with{is_authenticated:false}, re-burning it for the next login.registerPersonPropertiesandregisterPersonPropertiesOnce: any anonymous person-property write (e.g.first_generation_at, set on first generation which usually precedes login) emitted a$identify.With the anon id identified, the login-time
alias(installation_id → user_id)inbindUserIdis a silent no-op. That is the entire bug.The fix — one invariant
client.identify()is called with exactly one kind of id: the authenticated Firebaseuser_id, only insidebindUserId. The anonymousinstallation_idis never thedistinctIdof anidentify()call.All anonymous person-property writes (
$set/$set_once) now route through a newcapturePersonProperties()helper that fires them as a$set/$set_onceon a dedicated low-volumecomfy.desktop.person.setcapture event. In posthog-node,$seton a captured event updates the person profile without emitting$identify, so the id stays anonymous and the login alias merges. The same path is correct post-login (the distinct id is then the already-identified uid).Sites fixed: boot bind,
unbindUserId,registerPersonProperties,registerPersonPropertiesOnce, and the deferred-flush path.bindUserId'salias+ singleidentify(user_id)is unchanged and now actually merges.Scope / companion work
This fixes the desktop shell's own posthog-node events (the
installation_idstream). Desktop also shipsdesktop_device_idon cloud URLs (cloudUrl.ts) for a frontend-side stitch of the cloud-webview stream, but the embedded frontend has no telemetry pipeline in desktop builds yet — see #1069, which would relay frontend logs through desktop. A companion ComfyUI_frontend change firesidentify(uid)withdesktop_device_idfor that stream. Neither stream stitched while the anon id was burned; this PR unblocks the desktop one.Validation
telemetry.test.ts+experiments.test.ts: 72 passing. New assertions verify boot/logout/person-prop writes emit no$identifyon the anon id, andbindUserIdstill emits the alias + a singleidentify(uid)+app:user_logged_in.