Skip to content

feat(observability): one stable user id — Sentry now knows who the caller is - #247

Merged
BSalaeddin merged 1 commit into
mainfrom
puw/stable-user-id-in-sentry
Sep 17, 2026
Merged

BSalaeddin merged 1 commit into
mainfrom
puw/stable-user-id-in-sentry

Conversation

@BSalaeddin

Copy link
Copy Markdown
Collaborator

Why

$ grep -rn 'setUser' apps/caramel-app/src
(no output)

PostHog identifies on users.id (identity.ts:112) and Sentry was handed that id only as a
free-text context blob (posthog_distinct_id inside setContext('posthog', …)). Sentry does
nothing with context: "users affected" counts, user.id:<id> search and issue-to-account
attribution all read the user field, which nothing ever set. So every Caramel issue was
structurally anonymous — you could see that a route threw, never for whom.

Found during the /paid-user-watch fleet run (2026-09-16), whose whole premise is that you must be
able to read one user's story across systems before you judge them.

What changes

The id is set in the one place each side learns the identity — not sprinkled at call sites.

BrowserPostHogClientProvider, on every session transition. Deliberately moved above the
activeRef guard: Sentry and PostHog are separately configured, and a deploy without a PostHog key
must still produce attributable Sentry issues. (That guard is exactly why the old code had no
identity call on this path at all.) setSentryUser(null) on logout, so the next anonymous visitor
on a shared device does not keep reporting as the account that just left.

ServerwithRoute, immediately after auth.api.getSession. That single line covers every
route that declares auth, present and future, and clears the field for an anonymous
auth: 'optional' request so a reused server context cannot leak the previous caller's identity
onto someone else's error.

ID only. The same users.id in Sentry, PostHog and Postgres — one join key across all three.
No email, no name; sendDefaultPii stays off.

Tests

tests/unit/stable-user-id.test.ts — 4 cases: the payload is {id} and nothing else, logout clears
it, an authenticated withRoute request is attributed to the DB user id, and an anonymous
auth: 'optional' request clears it.

tests/unit/withRoute.test.ts's @sentry/nextjs mock gains setUser (it previously exported only
captureException, so the auth tests faulted on the missing export).

Local gates: tsc --noEmit clean · prettier --check clean · full app unit suite green, 79 files
/ 723 tests
.

🤖 Generated with Claude Code

…ller is

`grep setUser` over this repo returned nothing. PostHog identified on the DB
user id and Sentry got that id only inside a free-text context blob, so no
Sentry issue was attributable to an account: "users affected" was structurally
zero and `user.id:<id>` search matched nothing.

Two places, both the single place their side learns the identity: the browser
sets it on every session transition in PostHogClientProvider (outside the
PostHog-configured guard, because Sentry works even where PostHog is unset),
and the server sets it in withRoute right after `auth.api.getSession` — which
covers every authenticated API route at once and clears the field for anonymous
requests.

ID only: same `users.id` across Sentry, PostHog and Postgres, no email, no name,
sendDefaultPii stays off.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@BSalaeddin
BSalaeddin merged commit 21cf763 into main Sep 17, 2026
16 checks passed
@BSalaeddin
BSalaeddin deleted the puw/stable-user-id-in-sentry branch September 17, 2026 19:43
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