Skip to content

Fix session recorder and even tracker access token fetch#1331

Merged
BilalG1 merged 8 commits intodevfrom
fix-session-replays-access-token-fetch
Apr 14, 2026
Merged

Fix session recorder and even tracker access token fetch#1331
BilalG1 merged 8 commits intodevfrom
fix-session-replays-access-token-fetch

Conversation

@BilalG1
Copy link
Copy Markdown
Collaborator

@BilalG1 BilalG1 commented Apr 13, 2026

The gate delayed flushes until an access token resolved, but sendBatch already resolves the session itself via _getSession() at send time, so _lastKnownAccessToken was a redundant readiness check that caused head-of-session events to be silently dropped on slow auth init and suppressed uploads entirely when token fetch failed.

Summary by CodeRabbit

  • Refactor
    • Simplified analytics, session-replay, and event-tracking to stop periodic background access-token refreshes; flushing now occurs based on buffered data and lifecycle triggers.
  • Bug Fixes
    • Anonymous-user fallback tightened: anonymous identity is only applied when explicitly requested as "anonymous-if-exists," preventing unintended anonymous attribution.
  • Tests
    • Updated timing helper and removed token callback from test setups to align with the new flush behavior.

The gate delayed flushes until an access token resolved, but sendBatch
already resolves the session itself via _getSession() at send time, so
_lastKnownAccessToken was a redundant readiness check that caused
head-of-session events to be silently dropped on slow auth init and
suppressed uploads entirely when token fetch failed.
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
stack-auth-hosted-components Ready Ready Preview, Comment Apr 14, 2026 1:06am
stack-backend Ready Ready Preview, Comment Apr 14, 2026 1:06am
stack-dashboard Ready Ready Preview, Comment Apr 14, 2026 1:06am
stack-demo Ready Ready Preview, Comment Apr 14, 2026 1:06am
stack-docs Ready Ready Preview, Comment Apr 14, 2026 1:06am
stack-preview-backend Ready Ready Preview, Comment Apr 14, 2026 1:06am
stack-preview-dashboard Ready Ready Preview, Comment Apr 14, 2026 1:06am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 13, 2026

📝 Walkthrough

Walkthrough

Reworked analytics auth: EventTracker and SessionRecorder no longer accept or cache getAccessToken; analytics batches now obtain a session via getAnalyticsSession() supplied by the client app. GetCurrentPartialUserOptions.or renamed to 'anonymous-if-exists'.

Changes

Cohort / File(s) Summary
Client initialization
packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts
Stopped passing getAccessToken to analytics components; added/uses getAnalyticsSession() and calls await getAnalyticsSession() when sending batches.
Session recorder
packages/template/src/lib/stack-app/apps/implementations/session-replay.ts
Removed getAccessToken from deps and _lastKnownAccessToken; _tick() now flushes whenever events exist; _flush() no longer bails on missing token; removed silent/no-error run flags.
Event tracker
packages/template/src/lib/stack-app/apps/implementations/event-tracker.ts
Removed getAccessToken from deps and cached-token logic; _tick()/_flush() no longer gate on auth and flush based only on buffered events; removed no-error run flags.
Tests
packages/template/src/lib/stack-app/apps/implementations/event-tracker.test.ts
Removed getAccessToken from test setup; renamed helper advancePastAccessTokenRefreshadvancePastFlush and simplified timer advancement.
Common types
packages/template/src/lib/stack-app/common.ts
Changed GetCurrentPartialUserOptions<HasTokenStore>.or union member from 'anonymous''anonymous-if-exists'.
Example
examples/demo/src/app/token-staleness/page.tsx
Updated usePartialUser call to use or: 'anonymous-if-exists'.

Sequence Diagram(s)

sequenceDiagram
  participant ClientApp as ClientApp
  participant EventTracker as EventTracker
  participant SessionRecorder as SessionRecorder
  participant AnalyticsServer as AnalyticsServer

  ClientApp->>EventTracker: construct(projectId, sendBatch)
  ClientApp->>SessionRecorder: construct(projectId, sendBatch)
  Note over EventTracker,SessionRecorder: buffer events locally
  alt periodic tick when events exist
    EventTracker->>EventTracker: _tick() -> _flush()
    SessionRecorder->>SessionRecorder: _tick() -> _flush()
    EventTracker->>ClientApp: request getAnalyticsSession()
    SessionRecorder->>ClientApp: request getAnalyticsSession()
    ClientApp->>EventTracker: return analytics session
    ClientApp->>SessionRecorder: return analytics session
    EventTracker->>AnalyticsServer: sendAnalyticsEventBatch(events, session)
    SessionRecorder->>AnalyticsServer: sendSessionReplayBatch(events, session)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐇
I hopped through code where tokens slept,
I swapped the gates where waits had crept.
Now buffers sing and flushes roam,
Sessions fetched and batches home.
A little hop — analytics roam! 🎉

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title contains a typo ('even' instead of 'event') and is slightly misleading—the PR actually refactors token handling, not just fixes a fetch issue. Correct the typo and clarify the title to reflect the actual change, such as 'Remove redundant access token gate in session recorder and event tracker'.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The description clearly explains the problem (redundant token gate causing dropped events), the solution (removing the gate and handling sessions at send time), and the impact (prevents event loss during slow auth).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-session-replays-access-token-fetch

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 and usage tips.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 13, 2026

Greptile Summary

This PR removes the _lastKnownAccessToken readiness gate from SessionRecorder, which previously blocked periodic flushes until an access token had been fetched and cached. The auth check was redundant because sendBatch already resolves auth at send-time via await this._getSession() inside the closure passed to the recorder.

Confidence Score: 5/5

Safe to merge — only a P2 style finding remains.

The auth-gate removal is well-reasoned and the code path is sound. The single finding is a minor inconsistency where { noErrorLogging: true } was accidentally omitted in _tick, which could cause noise in error tracking but does not affect correctness.

packages/template/src/lib/stack-app/apps/implementations/session-replay.ts — the _tick flush call is missing { noErrorLogging: true }.

Important Files Changed

Filename Overview
packages/template/src/lib/stack-app/apps/implementations/session-replay.ts Removes _lastKnownAccessToken auth gate and getAccessToken dep; minor: noErrorLogging was accidentally dropped from the _tick flush call, inconsistent with all other flush sites.
packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts Removes the now-redundant getAccessToken prop from the SessionRecorder constructor; getAnalyticsAccessToken is still used by EventTracker so it is not dead code.

Sequence Diagram

sequenceDiagram
    participant Timer as setInterval (_tick)
    participant Recorder as SessionRecorder
    participant SendBatch as sendBatch closure
    participant Interface as _interface.sendSessionReplayBatch

    Note over Timer,Interface: Before (auth-gated)
    Timer->>Recorder: _tick()
    Recorder->>Recorder: refresh _lastKnownAccessToken (async)
    alt _lastKnownAccessToken is null
        Recorder-->>Timer: skip flush (events dropped)
    else token present
        Recorder->>SendBatch: _flush()
        SendBatch->>Interface: sendSessionReplayBatch(body, session, opts)
    end

    Note over Timer,Interface: After (auth removed)
    Timer->>Recorder: _tick()
    Recorder->>SendBatch: _flush() (if events queued)
    SendBatch->>Interface: sendSessionReplayBatch(body, await _getSession(), opts)
    Interface-->>SendBatch: Result
    SendBatch-->>Recorder: Result
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: packages/template/src/lib/stack-app/apps/implementations/session-replay.ts
Line: 325

Comment:
**Inconsistent `noErrorLogging` option removed in `_tick`**

Every other call to `runAsynchronously(() => this._flush(...))` in this file keeps `{ noErrorLogging: true }``stop()`, the batch-size threshold in `_startRecording`, and the `pagehide`/`visibilitychange` listener all do. Dropping it here means any unhandled exception from `_flush` (e.g., if `sendBatch`'s inner `await this._getSession()` rejects) will be forwarded to `captureError` on every 5-second tick, potentially flooding error-tracking dashboards for transient auth failures.

```suggestion
      runAsynchronously(() => this._flush({ keepalive: false }), { noErrorLogging: true });
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "Remove auth gate from session replay rec..." | Re-trigger Greptile

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/template/src/lib/stack-app/apps/implementations/session-replay.ts (1)

322-327: Add { noErrorLogging: true } for consistency.

All other runAsynchronously calls in this file (lines 171, 184, 287, 298) include { noErrorLogging: true }, but this one does not. Since _flush already logs warnings internally when errors occur, omitting the option here may cause redundant error output.

♻️ Suggested fix
   private _tick() {
     if (this._cancelled) return;
     if (this._events.length > 0) {
-      runAsynchronously(() => this._flush({ keepalive: false }));
+      runAsynchronously(() => this._flush({ keepalive: false }), { noErrorLogging: true });
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/template/src/lib/stack-app/apps/implementations/session-replay.ts`
around lines 322 - 327, The runAsynchronously call inside the private method
_tick currently omits the options object and should pass { noErrorLogging: true
} for consistency; update the invocation of runAsynchronously(() =>
this._flush({ keepalive: false })) in _tick to include the options parameter so
it becomes runAsynchronously(() => this._flush({ keepalive: false }), {
noErrorLogging: true }) to avoid duplicate error output (referencing the _tick
method, runAsynchronously helper, and _flush function).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/template/src/lib/stack-app/apps/implementations/session-replay.ts`:
- Around line 322-327: The runAsynchronously call inside the private method
_tick currently omits the options object and should pass { noErrorLogging: true
} for consistency; update the invocation of runAsynchronously(() =>
this._flush({ keepalive: false })) in _tick to include the options parameter so
it becomes runAsynchronously(() => this._flush({ keepalive: false }), {
noErrorLogging: true }) to avoid duplicate error output (referencing the _tick
method, runAsynchronously helper, and _flush function).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5e984cf6-d22d-4fba-93f7-36df88bada31

📥 Commits

Reviewing files that changed from the base of the PR and between 3102787 and 5a7c282.

📒 Files selected for processing (2)
  • packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts
  • packages/template/src/lib/stack-app/apps/implementations/session-replay.ts
💤 Files with no reviewable changes (1)
  • packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts

Same pattern as SessionRecorder: sendBatch already resolves the session
via _getSession() at send time, so the _lastKnownAccessToken readiness
check was redundant and delayed the first batch by an extra tick.
getAnalyticsAccessToken is now unused and removed from the construction
site.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/template/src/lib/stack-app/common.ts`:
- Around line 53-56: Add a temporary deprecated alias "anonymous" to the
GetCurrentPartialUserOptions union so existing callers of getPartialUser({ or:
"anonymous" }) continue to compile: update the union to include 'anonymous'
alongside 'anonymous-if-exists', add a comment "// TODO next-release" next to
the alias, and edit the existing inline note to reference the canonical
"anonymous-if-exists" value; also mirror this compatibility shim inside the
_getTokenPartialUserFromSession(...) implementation so it accepts "anonymous" as
equivalent to "anonymous-if-exists" until the next release.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a4cfb03e-3c15-471f-ac75-f04624e873d1

📥 Commits

Reviewing files that changed from the base of the PR and between 5a7c282 and ff20721.

📒 Files selected for processing (4)
  • packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts
  • packages/template/src/lib/stack-app/apps/implementations/event-tracker.test.ts
  • packages/template/src/lib/stack-app/apps/implementations/event-tracker.ts
  • packages/template/src/lib/stack-app/common.ts

Comment thread packages/template/src/lib/stack-app/common.ts
BilalG1 added 2 commits April 13, 2026 11:32
Use getPartialUser to detect a cached token; fall back to
getUser({ or: 'anonymous' }) so anonymous sign-up happens in the analytics
path itself rather than relying on a separate access-token gate. Also drop
the `noErrorLogging: true` flags from the _flush calls so unexpected
failures surface instead of being swallowed.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 641f7320-9a8b-4a36-8f20-85b21f0e50cb

📥 Commits

Reviewing files that changed from the base of the PR and between ff20721 and e616f50.

📒 Files selected for processing (4)
  • examples/demo/src/app/token-staleness/page.tsx
  • packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts
  • packages/template/src/lib/stack-app/apps/implementations/event-tracker.ts
  • packages/template/src/lib/stack-app/apps/implementations/session-replay.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/template/src/lib/stack-app/apps/implementations/event-tracker.ts

@BilalG1 BilalG1 requested a review from N2D4 April 13, 2026 19:51
@BilalG1 BilalG1 assigned N2D4 and unassigned BilalG1 Apr 13, 2026
@BilalG1 BilalG1 changed the title Remove auth gate from session replay recorder Fix session recorder and even tracker access token fetch Apr 13, 2026
@github-actions github-actions Bot assigned BilalG1 and unassigned N2D4 Apr 14, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts (1)

556-580: ⚠️ Potential issue | 🟠 Major

Don't move anonymous sign-up into the unload flush path.

This still resolves getUser({ or: "anonymous" }) on the send path. If the replay/event unload handlers only make the final batch upload keepalive, first-visit anonymous users can still lose the whole batch because the prerequisite user fetch/sign-up is a normal request. Warm the analytics session when recording starts or when the first item is buffered so unload only needs the keepalive upload.

Please verify the unload/pagehide flow in the adjacent files:

#!/bin/bash
set -euo pipefail

echo "== analytics session lookup =="
rg -n -C3 'getAnalyticsSession|sendSessionReplayBatch|sendAnalyticsEventBatch' \
  packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts

for f in \
  packages/template/src/lib/stack-app/apps/implementations/session-replay.ts \
  packages/template/src/lib/stack-app/apps/implementations/event-tracker.ts
do
  echo
  echo "== $f =="
  rg -n -C4 'pagehide|beforeunload|visibilitychange|keepalive|sendBatch' "$f"
done

Expected result: unload/pagehide should not require any pre-upload request before the keepalive batch send.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts`
around lines 556 - 580, The unload flow still calls getUser({or: "anonymous"})
via getAnalyticsSession() during sendBatch, causing a non-keepalive request; to
fix, eagerly warm and cache the analytics session when recording starts (or on
first buffered item) so unload handlers only use the cached session.
Specifically, in client-app-impl.ts ensure getAnalyticsSession() is invoked and
its result stored before calling this._sessionRecorder.start() and before
creating/starting this._eventTracker (or hook into SessionRecorder/EventTracker
to call and cache getAnalyticsSession() when the first event/replay is
buffered), then have the sendBatch closures use the cached session value instead
of calling getAnalyticsSession() each time.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts`:
- Around line 2693-2695: The check that returns null for anonymous tokens should
accept both the new and deprecated literal; update the conditional around
isAnonymous and options.or to treat "anonymous-if-exists" OR
"anonymous-if-exists[deprecated]" as equivalent (and add a comment // TODO
next-release) and also add the same temporary alias to the
GetCurrentPartialUserOptions type/enum so the old literal remains supported for
one release.

---

Duplicate comments:
In `@packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts`:
- Around line 556-580: The unload flow still calls getUser({or: "anonymous"})
via getAnalyticsSession() during sendBatch, causing a non-keepalive request; to
fix, eagerly warm and cache the analytics session when recording starts (or on
first buffered item) so unload handlers only use the cached session.
Specifically, in client-app-impl.ts ensure getAnalyticsSession() is invoked and
its result stored before calling this._sessionRecorder.start() and before
creating/starting this._eventTracker (or hook into SessionRecorder/EventTracker
to call and cache getAnalyticsSession() when the first event/replay is
buffered), then have the sendBatch closures use the cached session value instead
of calling getAnalyticsSession() each time.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a8da992a-68a4-4da1-b5b4-267e60e4d77e

📥 Commits

Reviewing files that changed from the base of the PR and between e616f50 and 4f6b9ac.

📒 Files selected for processing (2)
  • packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts
  • packages/template/src/lib/stack-app/common.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/template/src/lib/stack-app/common.ts

@BilalG1 BilalG1 merged commit b3ed56e into dev Apr 14, 2026
35 checks passed
@BilalG1 BilalG1 deleted the fix-session-replays-access-token-fetch branch April 14, 2026 01:32
BilalG1 added a commit that referenced this pull request Apr 14, 2026
Owned admin apps are constructed with `tokenStore: null`, which caused
EventTracker/SessionRecorder flushes to throw from
_ensurePersistentTokenStore() after #1331 removed the silencing.
BilalG1 added a commit that referenced this pull request Apr 14, 2026
Owned admin apps are constructed with `tokenStore: null`, which caused
EventTracker/SessionRecorder flushes to throw from
_ensurePersistentTokenStore() after #1331 removed the silencing.

<!--

Make sure you've read the CONTRIBUTING.md guidelines:
https://github.com/stack-auth/stack-auth/blob/dev/CONTRIBUTING.md

-->


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Improved analytics stability and privacy by restricting session
recording and event tracking to environments with required persistent
storage.
* **Tests**
* Adjusted a few end-to-end tests to skip when running against a local
emulator to reduce spurious failures.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
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.

2 participants