Setting the file. One moment.
Skill 104 · Resolving Ingestion Warnings
Subchapter 104.10
references/fixing-invalid-distinct-ids.mdMarkdown4 KBView on GitHub
Both warnings are the same bug class: something that isn’t a user identifier reached the distinct ID argument. The value’s shape decides which warning fires:
| Type | Severity | The junk value | What happened |
|---|---|---|---|
cannot_merge_with_illegal_distinct_id | warning | A blocklisted placeholder: undefined, null, NaN, [object Object], true, false, anonymous, guest, distinct_id, … | The identify/alias merge was refused — silently; the SDK call returned success. (If it weren’t, every user hitting the same bug would merge into one giant “undefined” person) |
skipping_event_invalid_distinct_id | error | Anything over 400 characters — a JWT, a serialized object, a URL, a concatenation bug | The event was dropped entirely. Every event sent with that value is silently lost until fixed |
posthog.identify(user.id) where user is not yet loaded → identify(undefined) → "undefined".posthog.alias(String(session.userId)) with a missing field → "undefined" / "null"; an object passed where a string was expected → "[object Object]"."anonymous" or "guest" for logged-out users — don’t; PostHog already handles anonymous users with its own IDs.posthog:execute-sql: SELECT timestamp, details FROM system.ingestion_warnings WHERE type IN ('cannot_merge_with_illegal_distinct_id', 'skipping_event_invalid_distinct_id') AND timestamp > now() - INTERVAL 7 DAY ORDER BY timestamp DESC LIMIT 20 (narrow to a single type to isolate one variant). The details JSON does most of the work: illegalDistinctId shows the placeholder (plus otherDistinctId, the real user it tried to link); the oversized variant shows the truncated distinctId and its length. The value’s shape names the bug.identify(, alias(, and capture( with an explicit distinctId — and trace where the argument can be undefined (typically a race with auth state) or receive a token/object.Guard every call so only a real, stable user identifier can reach the ID argument:
if (user?.id) {
posthog.identify(user.id)
}identify after authentication resolves (auth callback/effect), not on page load.identify eventually runs.Re-run the login/affected flow, re-query system.ingestion_warnings with posthog:execute-sql (filter type IN ('cannot_merge_with_illegal_distinct_id', 'skipping_event_invalid_distinct_id'), timestamp after your fix) — no new occurrences of either type — and confirm events arrive under the correct persons.