Repository navigation
feat(tracing-headers): allow an optional posthog-js cookie fallback - #116
DanielVisca wants to merge 2 commits into
Conversation
Server middleware may read the session id, and the distinct id of an identified visitor, from the posthog-js cookie when a request has neither tracing header. The option is off by default, honors the consent cookie, and checks session liveness like posthog-js. Co-Authored-By: Claude Opus 5.5 <[email protected]> Generated-By: PostHog Desktop Task-Id: 80f44d9e-704d-4f9c-93d2-bd37d55ab1c2
…a new profile The cookie fallback now uses the cookie distinct id for anonymous visitors too and sets $process_person_profile = false, instead of generating a random id. This keeps browser and backend events on one distinct id, as @posthog/next does, while the fallback still creates no new person profile. An explicit or authenticated distinct id overrides the cookie and is not made personless by the cookie $user_state. Generated-By: PostHog Desktop Task-Id: fe92e314-5188-4f31-95f6-3eb2d36dc3e1
dustinbyrne
left a comment
There was a problem hiding this comment.
The optional cookie fallback contract looks good, including anonymous identity retention and explicit-identity precedence. One non-blocking consent clarification is noted inline.
AI-assisted review.
|
|
||
| ### Requirement: Cookie fallback identity and consent | ||
|
|
||
| With the fallback on, the middleware SHALL read only the configured project's cookie, `ph_<token>_posthog` with `+`, `/`, `=` in the token replaced by `PL`, `SL`, `EQ`. It SHALL ignore the cookie when the consent cookie `__ph_opt_in_out_<token>` is no-like, or absent under configured opt-out by default. It SHALL use the cookie `distinct_id` for identified and anonymous visitors. |
There was a problem hiding this comment.
1. [P2] Define pending consent under opt-out-by-default
Severity: MINOR
Disposition: OPTIONAL
Relationship: Introduced specification precision gap—not a runtime regression.
Location: openspec/specs/tracing-headers/spec.md:279, RIGHT; mirror in the archived delta at line 27.
Consider covering present-but-pending consent, as well as an absent consent cookie.
With fallback enabled, no tracing headers, a live project cookie, and opt-out-by-default enabled, either of these consent values is present but neither yes-like nor no-like:
__ph_opt_in_out_test-token=
__ph_opt_in_out_test-token=unexpectedThe new sentence explicitly requires rejection for no-like values and absence, but leaves these pending values unspecified. The expected result is that neither cookie identity nor cookie session is used.
At pinned revision 69814c77c675918a658e8dad4a02dfdb152398e4:
- Browser
consent.ts:93–96classifies these values as pending, and57–60rejects pending consent under opt-out-by-default. - Node already correctly rejects them through
!isYesLike(consent), with explicit empty/unrecognized-value tests.
An implementation translating only the listed rejection conditions could therefore link identity/session without granted consent. The prose does not require that permissive result; it simply leaves the boundary unspecified.
Consider replacing the condition in both canonical and archived text with:
It SHALL ignore the cookie when the consent cookie is no-like, or when opt-out-by-default is configured and consent is not yes-like.
A corresponding scenario would preserve that boundary:
#### Scenario: pending consent is not linked under opt-out-by-default
- **GIVEN** cookie fallback and opt-out-by-default are enabled
- **AND** the request has no tracing headers and a live project cookie
- **AND** the project's consent cookie is present with an empty or unrecognized value
- **WHEN** capture is called without an explicit distinct id
- **THEN** neither the cookie distinct id nor the cookie session id is usedVerification and impact limits: Deterministic source/contract comparison establishes this omission; no failing SDK execution is claimed or applicable. Base had no fallback contract, while head introduces this condition. Browser consent APIs ordinarily write 0 or 1, so the additional trigger requires externally written, legacy, migrated, or malformed state. No production incident or current Node defect was established.
Problem
Server SDKs link backend events to a browser session only through the
X-POSTHOG-*tracing headers, and most apps never settracing_headers. posthog-js already keeps the distinct ID, the session ID and the user state in its first-party cookie, and the browser sends that cookie on every same-site request. posthog-python and posthog-node now add an off-by-default option to read it, but the spec lists only headers as server input.Changes
This adds three requirements to
tracing-headers, with scenarios:PL/SL/EQtoken replacement), honors the consent cookie and opt-out by default, and uses the distinct ID only for identified visitors.The change is synced into
openspec/specs/tracing-headers/spec.mdand archived underopenspec/changes/archive/2026-10-09-add-optional-posthog-js-cookie-fallback/.Implementations:
How did you test this?
openspec validate tracing-headers --type spec --strictpasses.openspec validate --specs --strict(CLI 1.14.1) reports the same failures in other specs with and without this change.Created with PostHog from a Slack thread
🤖 Generated with Claude Code