Skip to content

Identify authenticated PostHog users - #2326

Merged
Asherlc merged 2 commits into
mainfrom
Asherlc/issue-2321-posthog-identification
Jul 30, 2026
Merged

Asherlc merged 2 commits into
mainfrom
Asherlc/issue-2321-posthog-identification

Conversation

@Asherlc

@Asherlc Asherlc commented Jul 30, 2026

Copy link
Copy Markdown
Owner

Summary

  • identify the validated web user in PostHog with their stable database ID, name, and email
  • reset the PostHog identity after successful logout while preserving it when logout fails
  • cover the analytics adapter and authenticated, anonymous, and logout lifecycle paths with TDD regressions
  • document the implementation plan and current PostHog identity guidance

Validation

  • pnpm exec vitest run packages/web/src/lib/posthog.test.ts packages/web/src/lib/auth-context.test.tsx (13 passed)
  • pnpm test (14,881 passed, 21 skipped)
  • pnpm lint
  • pnpm tsc --noEmit
  • pnpm tsc --noEmit in packages/server, packages/web, and packages/mobile
  • pnpm build in packages/web

Fixes #2321

Summary by Sourcery

Align web analytics with authentication by tying PostHog identity to the authenticated user lifecycle and documenting the approach.

New Features:

  • Identify authenticated web users in PostHog using their stable ID and profile properties.
  • Expose helpers to set and clear PostHog user identity from the web client.

Enhancements:

  • Integrate PostHog identification and reset with auth bootstrap and logout flows, preserving identity on logout failures.
  • Add regression tests covering PostHog user identification, reset behavior, and auth lifecycle analytics interactions.

Documentation:

  • Add a TDD implementation plan documenting PostHog user identification behavior, scope, and testing strategy.

Summary by cubic

Identify authenticated web users in PostHog and keep identity in sync with auth, including resets on logout and session changes. Logout flow is sequenced: server logout completes, then analytics identity resets, then we redirect.

  • New Features
    • AuthProvider identifies on bootstrap with stable id, name, and email; anonymous visitors are not identified.
    • If the session ends or the user changes on rebootstrap, reset first, then identify the new user.
    • On logout success, reset PostHog and then call redirectToLogin; on failure, keep identity, do not navigate, and capture the error.
    • Added identifyPostHogUser/resetPostHogUser in packages/web via posthog-js, with adapter and auth lifecycle tests and updated docs.

Written for commit 00cde77. Summary will update on new commits.

Review in cubic

Copilot AI review requested due to automatic review settings July 30, 2026 01:12
@Asherlc Asherlc linked an issue Jul 30, 2026 that may be closed by this pull request
@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

@cursor

cursor Bot commented Jul 30, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @Asherlc, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@Asherlc, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 10 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 5ab14502-303e-48bf-8777-3c0df79a2ea8

📥 Commits

Reviewing files that changed from the base of the PR and between 9e06a07 and 00cde77.

📒 Files selected for processing (7)
  • docs/superpowers/plans/2026-07-29-posthog-user-identification.md
  • packages/web/src/lib/auth-context.test.tsx
  • packages/web/src/lib/auth-context.tsx
  • packages/web/src/lib/auth.test.ts
  • packages/web/src/lib/auth.ts
  • packages/web/src/lib/posthog.test.ts
  • packages/web/src/lib/posthog.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai

sourcery-ai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Adds a typed PostHog user identity lifecycle to the web app by introducing identify/reset helpers, wiring them into auth bootstrap and logout flows, and covering the behavior with focused unit and auth-context tests plus accompanying documentation of the TDD plan.

Sequence diagram for PostHog identity during auth bootstrap and logout

sequenceDiagram
  participant AuthProvider
  participant AuthAdapter
  participant PostHogAdapter
  participant Telemetry

  AuthProvider->>AuthAdapter: fetchCurrentUser
  alt [authenticated user]
    AuthProvider->>PostHogAdapter: identifyPostHogUser
  else [unauthenticated visitor]
    Note over AuthProvider,PostHogAdapter: No identifyPostHogUser call
  end

  AuthProvider->>AuthAdapter: logout
  alt [logout succeeds]
    AuthProvider->>PostHogAdapter: resetPostHogUser
  else [logout fails]
    AuthProvider->>Telemetry: captureException
    Note over AuthProvider,PostHogAdapter: No resetPostHogUser call
  end
Loading

File-Level Changes

Change Details Files
Introduce typed helpers to identify and reset PostHog users and test them.
  • Extend the PostHog test mock to include identify and reset methods and add unit tests for identifying authenticated users (including nullable email) and resetting browser identity.
  • Implement identifyPostHogUser to call posthog.identify with the AuthUser ID and person properties, and resetPostHogUser to delegate to posthog.reset.
packages/web/src/lib/posthog.test.ts
packages/web/src/lib/posthog.ts
Wire PostHog identity lifecycle into the AuthProvider bootstrap and logout flows with regression tests.
  • Inject identifyPostHogUser and resetPostHogUser into the auth context, identifying the user after a successful bootstrap when a user is present and resetting PostHog only after a successful logout.
  • Add an auth-context test suite that mocks auth, telemetry, and PostHog adapters to verify identification on authenticated bootstrap, no identification/reset for anonymous bootstrap, reset-only-after-successful-logout, and no reset on logout failure while still capturing the exception.
packages/web/src/lib/auth-context.tsx
packages/web/src/lib/auth-context.test.tsx
Document the PostHog user identification plan and testing strategy.
  • Add a TDD-focused implementation plan describing goals, behavior, scope, current evidence, test strategy, file structure, and task checklist for PostHog user identification on web.
docs/superpowers/plans/2026-07-29-posthog-user-identification.md

Assessment against linked issues

Issue Objective Addressed Explanation
#2321 Identify authenticated users in PostHog by calling posthog.identify() after login with a stable user ID and relevant traits (email, name).
#2321 Clear analytics identity on logout by calling posthog.reset() so sessions do not bleed between users.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Identify authenticated PostHog users in web auth lifecycle

✨ Enhancement 🧪 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

• Identify authenticated web users in PostHog using stable user ID, name, and email.
• Reset PostHog identity only after logout succeeds to avoid desynchronization.
• Add regression tests for bootstrap, anonymous, and logout success/failure paths; document plan.
Diagram

graph TD
A["AuthProvider"] --> B["auth.ts (API)"] --> C{{"Server auth endpoints"}}
A --> D["posthog.ts (adapter)"] --> E{{"posthog-js SDK"}}
A --> F["telemetry.ts"] --> G{{"Sentry"}}
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Identify/reset directly in auth-context (no adapter)
  • ➕ Fewer exported helper functions/files involved
  • ➕ Less indirection for a small integration
  • ➖ Harder to unit test PostHog interactions in isolation
  • ➖ Duplicates SDK details across call sites if expanded later
2. Identify whenever user state changes (not just bootstrap)
  • ➕ More robust if the app supports in-app login without full reload
  • ➕ Automatically handles future flows that set user after bootstrap
  • ➖ Requires carefully handling transitions (null→user, user→null) to avoid double-identify/reset
  • ➖ May expand scope beyond the immediate issue if user changes come from multiple sources

Recommendation: Current approach (small PostHog adapter + call sites in AuthProvider) is a good balance: it centralizes identity behavior at the auth lifecycle boundary, keeps PostHog SDK usage contained, and is well covered by tests. If future work introduces in-app login flows that update user state post-bootstrap, consider extending this to identify/reset on user state transitions rather than only in retryBootstrap/logout.

Files changed (5) +240 / -1

Enhancement (2) +17 / -0
auth-context.tsxWire AuthProvider bootstrap/logout to PostHog identify/reset +5/-0

Wire AuthProvider bootstrap/logout to PostHog identify/reset

• On successful bootstrap, identifies the current user to PostHog when present. On successful logout, resets PostHog identity; on logout failure, reports via telemetry and rethrows without resetting analytics.

packages/web/src/lib/auth-context.tsx

posthog.tsAdd typed PostHog user identity helpers +12/-0

Add typed PostHog user identity helpers

• Adds identifyPostHogUser(AuthUser) to set PostHog distinct id and person properties, and resetPostHogUser() to clear identity via posthog.reset().

packages/web/src/lib/posthog.ts

Tests (2) +168 / -1
auth-context.test.tsxAdd AuthProvider analytics identity regression tests +121/-0

Add AuthProvider analytics identity regression tests

• Introduces jsdom hook-based tests validating that auth bootstrap identifies authenticated users, anonymous bootstrap does nothing, logout success resets PostHog, and logout failure preserves identity while reporting the error.

packages/web/src/lib/auth-context.test.tsx

posthog.test.tsExpand PostHog adapter tests to cover identify/reset helpers +47/-1

Expand PostHog adapter tests to cover identify/reset helpers

• Extends unit coverage to assert identify uses the stable user ID plus name/email properties (including nullable email) and reset delegates to the PostHog SDK reset call.

packages/web/src/lib/posthog.test.ts

Documentation (1) +55 / -0
2026-07-29-posthog-user-identification.mdDocument TDD plan for PostHog user identification lifecycle +55/-0

Document TDD plan for PostHog user identification lifecycle

• Adds a written plan describing the desired PostHog identify/reset behavior tied to auth bootstrap and logout. Captures scope, test strategy, and implementation tasks for web-only analytics identity.

docs/superpowers/plans/2026-07-29-posthog-user-identification.md

@qodo-code-review

qodo-code-review Bot commented Jul 30, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 167 rules

Grey Divider


Action required

1. Reset happens after redirect ✓ Resolved 🐞 Bug ≡ Correctness
Description
AuthProvider calls resetPostHogUser() only after awaiting doLogout(), but doLogout() sets
window.location.href before returning and does not check response.ok. This can prevent the PostHog
reset from executing before page unload and can also reset analytics even when the server logout
request returned an HTTP error (i.e., logout didn’t actually succeed).
Code

packages/web/src/lib/auth-context.tsx[R53-54]

      await doLogout();
+      resetPostHogUser();
Relevance

●● Moderate

No historical evidence on resetting analytics before logout redirect / only after response.ok.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The PR-added reset call is executed only after doLogout() completes, but doLogout() initiates
navigation and does not validate HTTP success, so the reset may not reliably execute and may run
even if logout failed at the HTTP layer.

packages/web/src/lib/auth-context.tsx[49-59]
packages/web/src/lib/auth.ts[157-161]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`resetPostHogUser()` is invoked after `await doLogout()`, but `doLogout()` triggers navigation (`window.location.href = "/login"`) and does not validate the HTTP response. This makes the reset unreliable (may not run before unload) and can treat HTTP failures as “successful logout”.

### Issue Context
- `AuthProvider.logout()` currently does:
 - `await doLogout();`
 - `resetPostHogUser();`
- `doLogout()` performs a POST and then immediately assigns `window.location.href`, without checking `res.ok`.

### Fix Focus Areas
- packages/web/src/lib/auth-context.tsx[49-59]
- packages/web/src/lib/auth.ts[157-161]

### Suggested fix
1. Change `packages/web/src/lib/auth.ts::logout()` to:
  - `const res = await fetch(...)`
  - if `!res.ok`, throw (or return a failure) and **do not** redirect.
  - perform redirect only after success.
2. Ensure PostHog reset happens *before* redirect/page unload. Options:
  - Move `resetPostHogUser()` into `logout()` immediately before setting `window.location.href`, or
  - Refactor `logout()` to not navigate; let `AuthProvider` call `resetPostHogUser()` and then navigate deterministically.
3. Add/adjust tests to cover:
  - reset occurs before redirect is initiated
  - non-2xx logout does not reset analytics

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. PostHog guidance claim lacks citation ✓ Resolved 📘 Rule violation § Compliance
Description
The docs state a specific PostHog guidance behavior but do not include an adjacent primary-source
link in the same paragraph/line area. This can leave third-party behavior claims unverifiable and
out of compliance with documentation citation requirements.
Code

docs/superpowers/plans/2026-07-29-posthog-user-identification.md[19]

+- The current PostHog guidance says to identify as soon as the frontend knows the authenticated user and to reset on logout.
Relevance

●●● Strong

Docs citation rule enforced; adjacent primary-source citations accepted in similar docs PRs (#2291,
#2047).

PR-#2291
PR-#2047

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 1505719 requires third-party behavior claims in docs to have an adjacent
primary-source hyperlink. The statement at line 19 asserts PostHog guidance behavior but has no
nearby citation.

Rule 1505719: Cite third-party behavior claims in docs with primary sources
docs/superpowers/plans/2026-07-29-posthog-user-identification.md[15-20]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A third-party behavior claim about PostHog guidance is made without an adjacent primary-source citation.

## Issue Context
The file includes a PostHog docs link earlier, but the specific behavior claim (`identify ... and reset on logout`) appears later without an adjacent link, which violates the documentation requirement for citing third-party behavior claims.

## Fix Focus Areas
- docs/superpowers/plans/2026-07-29-posthog-user-identification.md[19-19]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. No reset on null bootstrap ✓ Resolved 🐞 Bug ≡ Correctness
Description
retryBootstrap() identifies the user when fetchCurrentUser() returns one, but never resets PostHog
when bootstrap later resolves to null (unauthenticated). If a user was previously identified (e.g.,
session invalidated or account changed without an explicit logout), subsequent analytics events can
remain attributed to the prior user because PostHog identity is not cleared on this transition.
Code

packages/web/src/lib/auth-context.tsx[R34-36]

+      if (currentUser) {
+        identifyPostHogUser(currentUser);
+      }
Relevance

●● Moderate

No historical evidence on resetting analytics identity when bootstrap transitions to null
unauthenticated.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The PR adds identification on bootstrap, but there is no corresponding reset when bootstrap results
in null, so the code does not handle authenticated→unauthenticated transitions for analytics
identity.

packages/web/src/lib/auth-context.tsx[28-37]
packages/web/src/lib/auth-context.test.tsx[59-71]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`retryBootstrap()` calls `identifyPostHogUser(currentUser)` when a user is returned, but does nothing when `currentUser` is `null`. After this PR, the app can enter a state where auth is unauthenticated but PostHog remains identified from a previous authenticated state.

### Issue Context
This can happen if auth becomes invalidated without going through the explicit logout path (expired session, server-side revocation, switching accounts in another tab, etc.).

### Fix Focus Areas
- packages/web/src/lib/auth-context.tsx[28-43]
- packages/web/src/lib/auth-context.test.tsx[59-71]

### Suggested fix
1. Track the previously identified user id in `AuthProvider` (e.g., `useRef<string | null>`).
2. In `retryBootstrap()` after fetching:
  - If `previousUserId` is non-null and `currentUser` is null: call `resetPostHogUser()`.
  - If `currentUser` exists and differs from `previousUserId`: consider calling `resetPostHogUser()` before `identifyPostHogUser(currentUser)` to avoid cross-account leakage.
3. Update tests:
  - Add a regression that starts authenticated then bootstraps to `null` and asserts `resetPostHogUser()` is called.
  - Adjust/replace the current “does not reset on unauth bootstrap” assertion to reflect the intended transition-aware behavior.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread docs/superpowers/plans/2026-07-29-posthog-user-identification.md Outdated
Comment thread packages/web/src/lib/auth-context.tsx Outdated
Comment thread packages/web/src/lib/auth-context.tsx
@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Storybook previews for 6ab00376 are ready:

This comment updates automatically on each PR push.

@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

@Asherlc

Asherlc commented Jul 30, 2026

Copy link
Copy Markdown
Owner Author

Addressed all three actionable Qodo findings in 00cde77:

  • Logout now treats non-2xx responses as failures, preserves the authenticated/analytics state on failure, and sequences server logout → PostHog reset → navigation on success. Regression tests assert both failure behavior and reset-before-redirect ordering.
  • The PostHog guidance claim now has an adjacent primary-source documentation link.
  • Auth bootstrap now tracks the identified user: identified→anonymous resets, and user A→user B resets before identifying B. Both transitions have regression coverage.

Focused auth/PostHog validation passes (41 tests), all four TypeScript checks pass, and the web production build passes.

@Asherlc
Asherlc merged commit d644426 into main Jul 30, 2026
103 checks passed
@Asherlc
Asherlc deleted the Asherlc/issue-2321-posthog-identification branch July 30, 2026 01:54
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.

Add PostHog user identification

2 participants