Skip to content

fix(alias): include distinct_id in $create_alias event properties - #819

Draft
posthog[bot] wants to merge 1 commit into
mainfrom
posthog/android-alias-distinct-id-property
Draft

posthog[bot] wants to merge 1 commit into
mainfrom
posthog/android-alias-distinct-id-property

Conversation

@posthog

@posthog posthog Bot commented Sep 29, 2026

Copy link
Copy Markdown
Contributor

💡 Motivation and Context

The client-side alias(alias) path builds the $create_alias event with only an alias property. The cross-SDK alias contract's client acceptance scenario (acceptance/public/alias.feature, the @client scenario) requires the enqueued event's properties to include both alias and distinct_id, so a consumer reading only the event properties can resolve both sides of the linkage. See the Alias spec.

Compliance matrix note (compliance/posthog-android.md → n1, scored ❌ Fail):

SDK currently: alias(alias: String) builds props["alias"] = alias and calls capture($create_alias, properties = props) but never sets props["distinct_id"]. buildProperties() also does not inject distinct_id for $create_alias events.

Backwards compatibility: Backward-compatible — adding props["distinct_id"] = distinctId is purely additive to the event payload.

Remediation: Set distinct_id alongside alias in the $create_alias properties map.

The change

PostHog.alias(...) now sets props["distinct_id"] to the current ambient distinct id alongside alias. Nothing else moves: the top-level distinct_id, the ambient identity, the person-processing guard, and every other property are untouched.

buildProperties() has an unrelated session-replay branch that fills in a blank distinct_id property; it only writes when the value is null/blank, so it does not interact with this change.

Why it's backwards-compatible: it adds one property to one event type. No existing property changes value, no API signature changes, no behavior is removed. Existing callers and downstream consumers see a strict superset of what they saw before.

Server-side aliasStateless(distinctId, alias) is left alone — the server acceptance scenario only requires the top-level distinct_id, which captureStateless already sets correctly.

💚 How did you test it?

  • Extended the existing captures an alias event unit test to assert properties["distinct_id"] equals the event's top-level distinct id.
  • ./gradlew :posthog:test --tests "com.posthog.PostHogTest" — passes.
  • ./gradlew spotlessCheck — clean.

No manual/device testing was performed.

📝 Checklist

  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • I updated the docs if needed.
  • No breaking change or entry added to the changelog.

If releasing new changes

  • Ran pnpm changeset to generate a changeset file

Follow-up work

Other open posthog-android gaps in the same matrix that are out of scope here (one contract per PR): Capture Exception (missing top-level $exception_type/$exception_message), Shutdown (close() never flushes), Screen ($screen_name precedence), and the Feature Flag Called Tracker allowlist gap shared across 8 SDKs.

🤖 Agent context

Autonomy: Fully autonomous

Produced by a scheduled SDK-compliance agent (Claude Opus 5 via PostHog Desktop) that reads the compliance matrices in PostHog/sdk-specs and implements one backwards-compatible gap per run. This cell was picked over the other open posthog-android gaps because it is a core identity contract, the remediation is a single additive line with an unambiguous acceptance scenario behind it, and no open PR or branch was already addressing it.

One judgement call worth flagging for the reviewer: the Alias spec's prose says "Some stateless/server-oriented implementations also duplicate the source id into properties.distinct_id; audited mobile client helpers do not require that duplication," which sits in tension with the @client acceptance scenario that explicitly asserts the property. This PR follows the acceptance scenario (and the matrix verdict). If the intent is really that mobile clients should not duplicate it, the fix belongs in sdk-specs instead and this PR should be closed.

Agent-authored — requires human review; not self-merged.


Created with PostHog Desktop

🤖 Generated with Claude Code

The client `alias(alias)` path built `$create_alias` properties containing only
`alias`. The cross-SDK alias contract's client acceptance scenario requires the
enqueued event's properties to include both `alias` and `distinct_id`.

Purely additive: an extra property on one event type, no change to the top-level
distinct id, ambient identity, or any existing property.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Generated-By: PostHog Desktop
Task-Id: f85eba86-15aa-428c-b47e-b7c63215bf73
@github-actions

Copy link
Copy Markdown
Contributor

posthog-android Compliance Report

Date: 2026-09-29 06:16:39 UTC
Duration: 118008ms

✅ All Tests Passed!

46/46 tests passed


Capture Tests

✅ 29/29 tests passed

View Details
Test Status Duration
Format Validation.Event Has Required Fields ✅ 271ms
Format Validation.Event Has Uuid ✅ 26ms
Format Validation.Event Has Lib Properties ✅ 22ms
Format Validation.Distinct Id Is String ✅ 22ms
Format Validation.Token Is Present ✅ 21ms
Format Validation.Custom Properties Preserved ✅ 23ms
Format Validation.Event Has Timestamp ✅ 18ms
Retry Behavior.Retries On 503 ✅ 7022ms
Retry Behavior.Does Not Retry On 400 ✅ 4022ms
Retry Behavior.Does Not Retry On 401 ✅ 4020ms
Retry Behavior.Respects Retry After Header ✅ 7023ms
Retry Behavior.Implements Backoff ✅ 17031ms
Retry Behavior.Retries On 500 ✅ 7017ms
Retry Behavior.Retries On 502 ✅ 7015ms
Retry Behavior.Retries On 504 ✅ 7018ms
Retry Behavior.Max Retries Respected ✅ 17019ms
Deduplication.Generates Unique Uuids ✅ 30ms
Deduplication.Preserves Uuid On Retry ✅ 7012ms
Deduplication.Preserves Uuid And Timestamp On Retry ✅ 12018ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 7018ms
Deduplication.No Duplicate Events In Batch ✅ 30ms
Deduplication.Different Events Have Different Uuids ✅ 17ms
Compression.Sends Gzip When Enabled ✅ 16ms
Batch Format.Uses Proper Batch Structure ✅ 17ms
Batch Format.Flush With No Events Sends Nothing ✅ 9ms
Batch Format.Multiple Events Batched Together ✅ 27ms
Error Handling.Does Not Retry On 403 ✅ 4018ms
Error Handling.Does Not Retry On 413 ✅ 4018ms
Error Handling.Retries On 408 ✅ 5027ms

Feature_Flags Tests

✅ 17/17 tests passed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id ✅ 28ms
Request Payload.Flags Request Uses V2 Query Param ✅ 21ms
Request Payload.Flags Request Hits Flags Path Not Decide ✅ 17ms
Request Payload.Flags Request Omits Authorization Header ✅ 22ms
Request Payload.Token In Flags Body Matches Init ✅ 31ms
Request Payload.Groups Round Trip ✅ 21ms
Request Payload.Groups Default To Empty Object ✅ 23ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False ✅ 19ms
Request Payload.Disable Geoip Omitted Defaults To False ✅ 17ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key ✅ 18ms
Request Lifecycle.No Flags Request On Init Alone ✅ 7ms
Request Lifecycle.No Flags Request On Normal Capture ✅ 16ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests ✅ 35ms
Request Lifecycle.Mock Response Value Is Returned To Caller ✅ 21ms
Retry Behavior.Retries Flags On 502 ✅ 322ms
Retry Behavior.Retries Flags On 504 ✅ 318ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event ✅ 16ms

@o-mid

o-mid commented Oct 4, 2026

Copy link
Copy Markdown

#819 already matches what I had in the closed duplicate PR (#837) for PostHog.alias() and PostHogTest.

One gap for parity with the stateless/JVM path: aliasStateless still only sets alias in properties. Suggested follow-up (same one-liner + test):

PostHogStateless.kt — in aliasStateless, after props["alias"] = alias:

props["distinct_id"] = distinctId

PostHogStatelessTest.kt — in aliasStateless creates alias event:

assertEquals("user123", event.properties!!["distinct_id"])

Happy to open a tiny follow-up PR once this lands if that is easier than expanding the bot PR.

This branch has not been deployed

No deployments
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