Skip to main content
← Back to list
01Issue
BugOpenSwamp Club
AssigneesNone

Relationships

#967 $identify events are never consumed — anonymous web history never binds to the account

Opened by keeb · 7/4/2026

The web app emits $identify at sign-in (lib/auth.tsidentify(userId, getAnonymousId(req)) in lib/telemetry.ts) to link the anonymous browser device to the account. Nothing on the telemetry side consumes it: the stats consumer (services/telemetry/lib/consumers/stats.ts, updateIdentityMap) only binds a device when an event carries BOTH username and distinct_id — and $identify carries the user id as its distinct_id with the anon id buried in properties.$anon_id, so it is ingested and ignored.

The gap is structural, not incidental: the moment a user signs in, resolveDistinctId switches their web events' distinct_id from the anonymous IP+UA hash to their user id, so the anon hash never co-occurs with a username on any event. $identify was the only bridge, and only the emitting half was built.

Impact (measured in prod, 2026-07-04): anonymous web devices strand as ghost owners in the ClickHouse score_grants ledger. They can only earn presence grants (10/device-day), so the score impact is dust — 63,210 points across all stranded web ghosts combined — but the anon→identified funnel story is broken for web: pre-signup browsing history never folds into the profile, and analytics can't connect the visitor who converted to the account they became.

Steps to reproduce:

  1. Browse swamp-club.com signed out (events carry distinct_id = IP+UA hash).
  2. Sign in — a $identify event with properties.$anon_id is ingested.
  3. Inspect the Mongo identity_map entry for the user: the anon hash is never added; its grants stay owned by a ghost.

Fix sketch: consume $identify in the stats consumer — extract properties.$anon_id, resolve distinct_id (user id) to the username, and feed the pair through the existing updateIdentityMap path. Healing is retroactive by construction: identity is resolved at read time over the grant ledger, so the ghost's history folds into the user the moment the mapping lands, no grants rewritten. Caveat: the anon id is an IP+UA hash and churns with IPs, so only the most recent hash binds — the fix narrows the gap, it cannot close it retroactively for old hashes.

Explicitly NOT blocking the scoring read-flip cutover (keeb, 2026-07-04) — waived for scoring purposes; this is a funnel/analytics correctness bug. Context: scoring/scoring.md § "Course correction — reverse the shadow", prerequisite 2.

02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED

Open

7/4/2026, 8:03:36 PM

No activity in this phase yet.

03Sludge Pulse

Sign in to post a ripple.