Skip to content

Add browser error capture and error-only session replay via same-origin Sentry tunnel - #909

Merged
kody-bot merged 3 commits into
mainfrom
cursor/client-replay-837c
Jul 24, 2026
Merged

kody-bot merged 3 commits into
mainfrom
cursor/client-replay-837c

Conversation

@kentcdodds

@kentcdodds kentcdodds commented Jul 24, 2026 •

Copy link
Copy Markdown
Member

What

Adds client-side Sentry with strict privacy defaults, completing the observability rollout (#905, #906, #908):

  • Browser SDK (@sentry/browser): packages/worker/client/sentry-client.ts initializes from a kody:sentry meta tag that ssr-document.tsx renders when SENTRY_DSN is configured (the DSN is a publishable client key). No-ops entirely when the tag is absent (local dev / tests). The hydration error listener in entry.tsx also reports through it.
  • Error-only Session Replay: replaysSessionSampleRate: 0, replaysOnErrorSampleRate: 1.0 — nothing is recorded to Sentry during normal sessions; only the in-memory buffer around an error is uploaded. maskAllText and blockAllMedia are set explicitly because Kody sessions contain personal-assistant content.
  • Same-origin Sentry tunnel: new POST /sentry-tunnel route (packages/worker/src/app/handlers/sentry-tunnel.ts) forwards envelopes server-side, so the first-party CSP keeps connect-src 'self'. Abuse-safe: the envelope header's DSN must exactly match the Worker's own SENTRY_DSN (403 otherwise), bodies are capped at 10 MB, and the route 404s when no DSN is configured.
  • CSP: adds worker-src 'self' blob: for replay's compression Web Worker (spawning a blob worker already requires script execution, which script-src 'self' still gates).
  • tsconfig-worker.json includes client/sentry-config.ts (the SDK-free config module shared by SSR and client); docs updated in request-lifecycle.md and environment-variables.md.

Cost note

The client bundle grows from ~600 KB to ~850 KB minified (~75 KB gzip delta) — the Sentry CDN/lazy-load alternative is not available under script-src 'self', so static bundling is the CSP-correct option.

Verification

  • Unit: 7 new tests — tunnel handler (forward on DSN match, 403 mismatch, 404 unconfigured, 400 malformed, 502 upstream failure, ingest-URL derivation) and config parsing (rejects missing DSN and non-same-origin tunnels).
  • Manual browser E2E against the local dev server with a DSN set: meta tag rendered; SDK initialized with the Replay integration in buffer mode; a thrown error produced both the error-event envelope and the replay recording envelope through /sentry-tunnel (tunnel-envelopes: ['error_event', 'replay']); curl checks confirmed 403 on mismatched DSN and forward-attempt on match; CSP header includes the new worker-src directive.
  • Interesting SDK behavior confirmed empirically: replay only flushes its buffer after the error event is successfully sent, so a failing upstream leaves the buffer unflushed — worth knowing when debugging.
  • Full pre-push suite green: 387 test files, 18 Playwright E2E.
System recap — extends the app shell with client telemetry (medium risk)

Mode: recap · Base: main @ 52b06b46 · Head: branch tip

Classification: extends — new route on the app surface, CSP directive addition, and a browser SDK in the client bundle; no data-model or MCP changes.

Primitives touched

Primitive Group Impact
app-ui surfaces extends — client bundle initializes Sentry + error-only replay
app-router surfaces extends — new POST /sentry-tunnel route
security-headers platform extends — worker-src 'self' blob: added to CSP

System map

Browser errors flow from the client bundle through the same-origin tunnel into Sentry, keeping CSP locked.

Legend: green = composes (wiring only) · amber = extended by this PR · red = new primitive · gray = context (unchanged).

flowchart LR
	appUi["app-ui<br/>Browser app"]:::extended
	appRouter["app-router<br/>App routes"]:::extended
	csp["security-headers<br/>CSP"]:::extended
	sentry["Sentry ingest<br/>(external)"]:::untouched
	appUi -->|"envelopes via POST /sentry-tunnel"| appRouter
	appRouter -->|"forward only when DSN matches SENTRY_DSN"| sentry
	csp -->|"connect-src 'self' preserved; worker-src blob: for replay"| appUi
	classDef touched fill:#1a7f37,color:#fff
	classDef extended fill:#9a6700,color:#fff
	classDef added fill:#cf222e,color:#fff
	classDef untouched fill:#57606a,color:#fff
Loading

Invariants

Replay privacy: all text masked, all media blocked, no session recording without an error, sendDefaultPii: false. The tunnel cannot proxy to arbitrary destinations (DSN allowlist of exactly one).

Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features

    • Added browser error monitoring with Sentry, initialized from server-rendered config.
    • Added privacy-focused, error-only session replay (text masked, media blocked).
    • Added secure same-origin event forwarding via a /sentry-tunnel endpoint.
  • Documentation

    • Expanded contributing docs and environment-variable guidance for the client setup and replay/tunneling behavior.
  • Bug Fixes

    • Added stricter validation for Sentry config and tunnel submissions, including DSN matching and payload-size limits.
  • Tests

    • Added coverage for client config parsing and the /sentry-tunnel forwarding behavior.

@coderabbitai

coderabbitai Bot commented Jul 24, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 216cee08-23b6-438a-8517-4066f6ca3888

📥 Commits

Reviewing files that changed from the base of the PR and between 6a25489 and 5bd1adf.

📒 Files selected for processing (2)
  • packages/worker/src/app/handlers/sentry-tunnel.node.test.ts
  • packages/worker/src/app/handlers/sentry-tunnel.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/worker/src/app/handlers/sentry-tunnel.ts
  • packages/worker/src/app/handlers/sentry-tunnel.node.test.ts

📝 Walkthrough

Walkthrough

This change adds browser Sentry initialization and error-only Session Replay, passes configuration through SSR metadata, forwards validated envelopes through a same-origin worker route, updates CSP for Replay workers, and documents the integration.

Changes

Browser Sentry integration

Layer / File(s) Summary
Sentry configuration and SSR injection
packages/worker/client/sentry-config.ts, packages/worker/src/app/ssr-render.tsx, packages/worker/src/app/ssr-document.tsx, packages/worker/client/sentry-config.node.test.ts, packages/worker/tsconfig-worker.json
Defines and validates the client configuration, derives it from environment variables, injects it as a kody:sentry meta tag, and tests parsing behavior.
Browser initialization and error capture
packages/worker/client/sentry-client.ts, packages/worker/client/entry.tsx, packages/worker/package.json
Initializes @sentry/browser with privacy-restricted, error-only Replay and reports application errors without interrupting boot or error handling.
Envelope validation and forwarding
packages/worker/src/app/handlers/sentry-tunnel.ts, packages/worker/src/app/routes.ts, packages/worker/src/app/router.ts, packages/worker/src/app/handlers/sentry-tunnel.node.test.ts
Adds POST /sentry-tunnel, validates envelope size and DSN matching, forwards accepted envelopes, and tests response and upstream failure paths.
CSP and integration documentation
packages/worker/src/app/security-headers.ts, docs/contributing/architecture/request-lifecycle.md, docs/contributing/environment-variables.md
Adds worker-src 'self' blob: and documents Sentry configuration, error-only replay, and same-origin tunneling.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Browser
  participant SSR
  participant SentryTunnel
  participant Sentry
  SSR->>Browser: Render kody:sentry configuration
  Browser->>Browser: Initialize Sentry and capture client errors
  Browser->>SentryTunnel: POST /sentry-tunnel envelope
  SentryTunnel->>SentryTunnel: Validate size and DSN
  SentryTunnel->>Sentry: Forward matching envelope
  Sentry-->>SentryTunnel: Ingest response
  SentryTunnel-->>Browser: Return delivery status
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
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 (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: browser error capture and error-only Session Replay through a same-origin Sentry tunnel.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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 cursor/client-replay-837c

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.

@kody-bot
kody-bot marked this pull request as ready for review July 24, 2026 14:23
@github-actions

github-actions Bot commented Jul 24, 2026 •

Copy link
Copy Markdown
Contributor

🔎 Preview deployed: https://kody-pr-909.kody-a99.workers.dev

Worker: kody-pr-909
D1: kody-pr-909-db
KV: kody-pr-909-oauth-kv

Mocks:

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/worker/client/sentry-config.ts`:
- Around line 27-32: Update the tunnel validation in the sentry configuration
parser to accept paths beginning with a single slash while rejecting
scheme-relative values beginning with “//”. Add regression coverage for the
rejected scheme-relative tunnel in
packages/worker/client/sentry-config.node.test.ts at lines 32-50 and the
existing parser tests, preserving valid same-origin path behavior.

In `@packages/worker/src/app/handlers/sentry-tunnel.ts`:
- Around line 26-29: Update the DSN parsing in the handler around dsnUrl and
projectId to split the pathname into segments, use only the final segment as the
project ID, and preserve preceding segments when constructing the Sentry
envelope URL. Keep empty-project validation intact and add a regression test
covering a DSN hosted under a base path such as /sentry/456.
🪄 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 Plus

Run ID: 3f728266-5ade-479a-b042-f31b10464697

📥 Commits

Reviewing files that changed from the base of the PR and between 52b06b4 and 3c84506.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (15)
  • docs/contributing/architecture/request-lifecycle.md
  • docs/contributing/environment-variables.md
  • packages/worker/client/entry.tsx
  • packages/worker/client/sentry-client.ts
  • packages/worker/client/sentry-config.node.test.ts
  • packages/worker/client/sentry-config.ts
  • packages/worker/package.json
  • packages/worker/src/app/handlers/sentry-tunnel.node.test.ts
  • packages/worker/src/app/handlers/sentry-tunnel.ts
  • packages/worker/src/app/router.ts
  • packages/worker/src/app/routes.ts
  • packages/worker/src/app/security-headers.ts
  • packages/worker/src/app/ssr-document.tsx
  • packages/worker/src/app/ssr-render.tsx
  • packages/worker/tsconfig-worker.json

Comment thread packages/worker/client/sentry-config.ts Resolved

@cursor cursor 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.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 5bd1adf. Configure here.

timeline: '/timeline',
timelineApi: '/timeline.json',
health: '/health',
sentryTunnel: post('/sentry-tunnel'),

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.

Sentry tunnel hits write lease

Medium Severity

The new POST /sentry-tunnel route is treated like a mutating account write for signed-in users. Global createAccountWriteLeaseMiddleware runs on every POST except /account/delete, so authenticated browser envelopes can return 409 during account deletion, 503 when the lease is lost, or hold a D1 write lease for the entire upstream Sentry forward—including large error replay uploads—while anonymous POSTs skip the lease entirely.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5bd1adf. Configure here.

@kody-bot
kody-bot merged commit 958206d into main Jul 24, 2026
5 checks passed
@kody-bot
kody-bot deleted the cursor/client-replay-837c branch July 24, 2026 14:59
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.

3 participants