Skip to content

fix(desktop): register Sentry IPC protocol for custom session partition - #883

Merged
saddlepaddle merged 1 commit into
mainfrom
sentry
Jan 22, 2026
Merged

saddlepaddle merged 1 commit into
mainfrom
sentry

Conversation

@saddlepaddle

@saddlepaddle saddlepaddle commented Jan 22, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Fixes net::ERR_UNKNOWN_URL_SCHEME errors for sentry-ipc:// in production
  • The renderer uses a custom session partition (persist:superset) for isolation, but Sentry only registers its IPC protocol handler for session.defaultSession by default
  • Added getSessions option to include the custom partition
  • Moved initSentry() after app.whenReady() since session.fromPartition() requires the app to be ready

Test plan

  • Deploy to staging and verify Sentry errors are no longer appearing
  • Check Sentry dashboard for successful event delivery from desktop app

Summary by CodeRabbit

  • Chores

    • Desktop startup now initializes error reporting after the app is ready and uses a more compatible renderer communication mode.
    • Preload and build configuration updated so the error-reporting integration is consistently included at startup.
  • Bug Fixes

    • Improved reliability of error reporting during startup, with clearer diagnostics when initialization fails.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Jan 22, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Sentry init was moved from top-level startup to run after app.whenReady() inside the second-instance flow; the initializer became synchronous with top-level Sentry imports and IPCMode.Classic; preload now imports @sentry/electron; preload build externals list added @sentry/electron.

Changes

Cohort / File(s) Summary
Top-level startup
apps/desktop/src/main/index.ts
Removed top-level initSentry import/call; added initSentry() invocation inside the second-instance handler after app.whenReady().
Sentry module
apps/desktop/src/main/lib/sentry.ts
Changed initSentry from async→(): void; replaced dynamic import with top-level @sentry/electron/main and IPCMode imports; set ipcMode: IPCMode.Classic; include session.defaultSession and session.fromPartition("persist:superset"); adjusted failure message text.
Preload
apps/desktop/src/preload/index.ts
Added top-level import "@sentry/electron/preload"; to register renderer-side Sentry IPC.
Build config (preload)
apps/desktop/electron.vite.config.ts
Updated externalizeDepsPlugin exclude list to include ["@sentry/electron"] so @sentry/electron remains external for the preload build.

Sequence Diagram(s)

sequenceDiagram
    participant App as Electron App (index.ts)
    participant Second as Second-instance handler
    participant Sentry as Sentry module (lib/sentry.ts)
    participant Session as Electron Session
    participant Preload as Preload (renderer)

    App->>Second: second-instance startup event
    Second->>App: await app.whenReady()
    Second->>Sentry: call initSentry()
    Sentry->>Session: access defaultSession & fromPartition("persist:superset")
    Sentry->>Sentry: Sentry.init(..., ipcMode: IPCMode.Classic)
    Sentry-->>Second: init complete
    Second->>App: continue startup (create windows / load preload)
    Preload->>Sentry: preload registers renderer IPC (Classic)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 I waited for the app to wake, then hopped to set my trace,

With Classic ears I bind the threads across each renderer place.
Two sessions keep me busy, logs tucked safely in my den,
I nibble on a carrot when a crash comes now and then. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: registering Sentry's IPC protocol for a custom session partition to fix production errors.
Description check ✅ Passed The description covers the core issue, root cause, and solution; however, it lacks required sections like Type of Change, Related Issues, and complete Testing section per the template.

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

✨ Finishing touches
  • 📝 Generate docstrings

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.

@github-actions

github-actions Bot commented Jan 22, 2026 •

Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch
  • ✅ Electric Fly.io app

Thank you for your contribution! 🎉

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/desktop/src/main/lib/sentry.ts`:
- Around line 7-12: Add dotenv loading at the very top of the app entrypoint
before any other imports so runtime .env variables are available when env.main
is imported and initSentry() is called; specifically, in the entrypoint file
(the top of index.ts) call config({ path: resolve(... to the repo .env),
override: true }) from dotenv/ node:path before importing env.main or
initSentry, ensuring SENTRY_DSN_DESKTOP and other env vars are populated at
runtime.
🧹 Nitpick comments (1)
apps/desktop/src/main/lib/sentry.ts (1)

21-29: Extract hardcoded Sentry settings to constants.

Keeps configuration centralized and self-documenting (traces sample rate + partition string).

♻️ Proposed refactor
 import { env } from "../env.main";

+const TRACE_SAMPLE_RATE = 0.1;
+const SENTRY_SESSION_PARTITION = "persist:superset";
+
 let sentryInitialized = false;

@@
 		Sentry.init({
 			dsn: env.SENTRY_DSN_DESKTOP,
 			environment: env.NODE_ENV,
-			tracesSampleRate: 0.1,
+			tracesSampleRate: TRACE_SAMPLE_RATE,
 			sendDefaultPii: false,
 			// Register IPC protocol for custom session partition used by renderer
 			// This callback is invoked lazily after app.whenReady()
 			getSessions: () => [
 				session.defaultSession,
-				session.fromPartition("persist:superset"),
+				session.fromPartition(SENTRY_SESSION_PARTITION),
 			],
 		});

Comment thread apps/desktop/src/main/lib/sentry.ts Outdated
Comment on lines +7 to +12
/**
* Initialize Sentry in the main process.
* MUST be called before app.whenReady() for scheme registration to work.
* The getSessions callback is called lazily after app ready.
*/
export function initSentry(): void {

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.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the first 120 lines of index.ts to see env loading and import order
sed -n '1,120p' apps/desktop/src/main/index.ts

# Check where initSentry is called
rg -nP '\binitSentry\s*\(' apps/desktop/src/main/index.ts

# Check if initSentry is awaited anywhere
rg -nP '\bawait\s+initSentry\b' apps/desktop/src/main/index.ts

# Check for .then() chains on initSentry
rg -nP 'initSentry\s*\(\s*\)' apps/desktop/src/main/index.ts

Repository: superset-sh/superset

Length of output: 3442


🏁 Script executed:

#!/bin/bash
# Check the absolute first lines for env loading
head -20 apps/desktop/src/main/index.ts

# Search for dotenv or env loading anywhere in the file
rg -n 'dotenv|process\.env|loadEnv' apps/desktop/src/main/index.ts | head -20

Repository: superset-sh/superset

Length of output: 932


🏁 Script executed:

#!/bin/bash
# Check sentry.ts for env loading
rg -n 'dotenv|loadEnv|process\.env' apps/desktop/src/main/lib/sentry.ts

# Check electron.vite.config.ts
rg -n 'dotenv|loadEnv|process\.env' apps/desktop/electron.vite.config.ts | head -20

# Look for any .env file references or dotenv imports in apps/desktop
rg -r 'dotenv' apps/desktop --type ts --type js | head -20

Repository: superset-sh/superset

Length of output: 2543


🏁 Script executed:

#!/bin/bash
# Check first 20 lines of electron.vite.config.ts
sed -n '1,20p' apps/desktop/electron.vite.config.ts

Repository: superset-sh/superset

Length of output: 777


🏁 Script executed:

#!/bin/bash
# Search for config() call with override in both files
rg -n 'config\s*\(' apps/desktop/electron.vite.config.ts | grep -i override
rg -n 'config\s*\(' apps/desktop/src/main/index.ts

# Also search without override filter to see all config calls
rg -n 'config\s*\(' apps/desktop/electron.vite.config.ts

Repository: superset-sh/superset

Length of output: 205


🏁 Script executed:

#!/bin/bash
# Check sentry.ts for environment variable usage
cat -n apps/desktop/src/main/lib/sentry.ts

Repository: superset-sh/superset

Length of output: 1373


🏁 Script executed:

#!/bin/bash
# Check the env.main file
find apps/desktop -name "env.main*" -type f

# List the structure
ls -la apps/desktop/src/main/

Repository: superset-sh/superset

Length of output: 508


🏁 Script executed:

#!/bin/bash
# Check env.main.ts
cat -n apps/desktop/src/main/env.main.ts

Repository: superset-sh/superset

Length of output: 2032


🏁 Script executed:

#!/bin/bash
# Check if any .env file is present in desktop root
ls -la apps/desktop/.env* 2>/dev/null || echo "No .env files in apps/desktop root"

# Check package.json scripts to see if env is loaded before app start
jq '.scripts' apps/desktop/package.json 2>/dev/null | grep -i 'dev\|start' || echo "Checking full package.json"

# Look for any other dotenv imports/calls in src/main
rg -n 'dotenv|loadEnv' apps/desktop/src/main/ --type ts

Repository: superset-sh/superset

Length of output: 411


Add dotenv.config() with override to load .env variables before any imports in src/main/index.ts.

The environment variables must be loaded at runtime in the electron app entry point before initSentry() is imported. Currently, env.main tries to read process.env variables (including SENTRY_DSN_DESKTOP) but they are not available because .env is only loaded during the vite build configuration, not at app runtime. Add the following at the very top of apps/desktop/src/main/index.ts before any imports:

import { config } from "dotenv";
import { resolve } from "node:path";

config({ path: resolve(__dirname, "../../.env"), override: true });

This aligns with the requirement that initSentry() runs before app.whenReady() and ensures environment variables are accessible when env.main is imported.

🤖 Prompt for AI Agents
In `@apps/desktop/src/main/lib/sentry.ts` around lines 7 - 12, Add dotenv loading
at the very top of the app entrypoint before any other imports so runtime .env
variables are available when env.main is imported and initSentry() is called;
specifically, in the entrypoint file (the top of index.ts) call config({ path:
resolve(... to the repo .env), override: true }) from dotenv/ node:path before
importing env.main or initSentry, ensuring SENTRY_DSN_DESKTOP and other env vars
are populated at runtime.

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/desktop/src/main/lib/sentry.ts`:
- Around line 12-33: Hardcoded partition string "persist:superset" is
duplicated; extract it as an exported constant named SUPERSET_PARTITION in the
shared constants module, then replace the literal in initSentry's getSessions
(session.fromPartition(...)) with session.fromPartition(SUPERSET_PARTITION) and
replace the literal used for the BrowserWindow partition setting in the windows
main module with SUPERSET_PARTITION; add the appropriate import of
SUPERSET_PARTITION where initSentry (getSessions) and the window creation code
reference the partition.
🧹 Nitpick comments (1)
apps/desktop/src/main/lib/sentry.ts (1)

29-31: Extract the session partition id to a named constant.

This avoids drift if the partition id is reused elsewhere (e.g., BrowserWindow configuration). As per coding guidelines, extract hardcoded values to named constants.

♻️ Suggested refactor
-				session.fromPartition("persist:superset"),
+				session.fromPartition(SUPSERSET_SESSION_PARTITION),
const SUPSERSET_SESSION_PARTITION = "persist:superset";

Comment on lines +12 to 33
export function initSentry(): void {
if (sentryInitialized) return;

if (!env.SENTRY_DSN_DESKTOP || env.NODE_ENV !== "production") {
return;
}

try {
// Dynamic import to avoid bundler issues
const Sentry = await import("@sentry/electron/main");

Sentry.init({
dsn: env.SENTRY_DSN_DESKTOP,
environment: env.NODE_ENV,
tracesSampleRate: 0.1,
sendDefaultPii: false,
// Use Classic IPC mode instead of Protocol to avoid timing issues
// Protocol mode requires init before app ready, which is unreliable with bundlers
ipcMode: IPCMode.Classic,
// Include custom session partition used by our BrowserWindow
getSessions: () => [
session.defaultSession,
session.fromPartition("persist:superset"),
],
});

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.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n apps/desktop/src/main/lib/sentry.ts

Repository: superset-sh/superset

Length of output: 1504


🏁 Script executed:

ls -la apps/desktop/src/shared/ | head -20

Repository: superset-sh/superset

Length of output: 1178


🏁 Script executed:

cat apps/desktop/tsconfig.json 2>/dev/null || cat apps/desktop/tsconfig.base.json 2>/dev/null || cat tsconfig.json | head -50

Repository: superset-sh/superset

Length of output: 440


🌐 Web query:

@sentry/electron v7.5.0 IPCMode.Classic getSessions custom partition electron best practices

💡 Result:

Below are concise best practices for using @sentry/electron v7.5.0 with IPCMode.Classic and apps that use custom Electron sessions/partitions.

Key facts

  • The SDK option getSessions should return an array of Electron Session objects; default is [session.defaultSession]. [1]
  • IPCMode.Classic uses Electron IPC to forward events/scopes between renderers and the main process. [1]
  • Electron sessions are created/obtained with session.fromPartition(partition) and BrowserWindow can be created with a partition option (use "persist:name" for persistent sessions). [2][3]

Practical best practices

  1. Provide getSessions that returns every session you want Sentry to listen/attach to (including session.fromPartition('persist:my-part')) so renderer processes using custom partitions are covered:
    • Example: getSessions: () => [ session.defaultSession, session.fromPartition('persist:my-part') ]. [1][2]
  2. Call Sentry.init (main) and set getSessions early in main (before creating BrowserWindow instances using those partitions) so the SDK can configure IPC/handlers for those sessions. [1][2]
  3. Use persistent partitions ("persist:...") for windows that need persistent cookies/storage and ensure you reuse the exact same partition string across windows to share the session. [2][3]
  4. If you want session (release health) capture tied to main process vs window focus, choose the appropriate integration:
    • Default MainProcessSession captures sessions for the main process; use BrowserWindowSession in renderers if you prefer per-window sessions. You can remove or configure MainProcessSession via integrations. [4]
  5. Avoid creating a Session with options after it’s been used — Electron cannot change options for an already-created partition. Create partitions with the desired options before first use. [2]
  6. If using IPCMode.Classic on older Electron versions, test for namespace collisions (optional namespaced IPC is available in newer SDK versions) and ensure your preload scripts expose Sentry renderer setup appropriately. [1]
  7. For privacy/compliance, filter or redact PII in event/scope before sending; if some partitions should not report, exclude their sessions from getSessions. [1]

References

  • Sentry Electron options (ipcMode, getSessions). [1]
  • Electron session/fromPartition and BrowserWindow partition docs. [2][3]
  • Sentry MainProcessSession integration (session capture behavior). [4]

References:
[1] Sentry for Electron — Options (ipcMode, getSessions). (docs.sentry.io)
[2] Electron — session/fromPartition docs. (electronjs.org)
[3] Electron — BrowserWindow partition option. (electronjs.org)
[4] Sentry for Electron — MainProcessSession integration. (docs.sentry.io)

If you want, I can draft the exact getSessions snippet and an example Sentry.init config for your app.

Citations:


🏁 Script executed:

find apps/desktop/src -name "*ipc*" -type f

Repository: superset-sh/superset

Length of output: 121


🏁 Script executed:

rg "persist:superset" apps/desktop/src --type ts --type tsx

Repository: superset-sh/superset

Length of output: 91


🏁 Script executed:

find apps/desktop/src -name "*constants*" -type f | head -10

Repository: superset-sh/superset

Length of output: 571


🏁 Script executed:

rg "partition.*persist" apps/desktop/src --type ts --type tsx -A 2 -B 2

Repository: superset-sh/superset

Length of output: 91


🏁 Script executed:

rg "persist:superset" apps/desktop/src --type ts

Repository: superset-sh/superset

Length of output: 218


🏁 Script executed:

rg "partition.*persist" apps/desktop/src --type ts -A 2 -B 2

Repository: superset-sh/superset

Length of output: 423


🏁 Script executed:

find apps/desktop/src -name "*ipc-channels*" -o -name "*ipc*channels*"

Repository: superset-sh/superset

Length of output: 46


🏁 Script executed:

cat apps/desktop/src/shared/constants.ts

Repository: superset-sh/superset

Length of output: 2047


🏁 Script executed:

rg "PARTITION\|SESSION_PARTITION\|SUPERSET_PARTITION" apps/desktop/src --type ts

Repository: superset-sh/superset

Length of output: 46


🏁 Script executed:

rg "trpc" apps/desktop/src --type ts -l | head -5

Repository: superset-sh/superset

Length of output: 299


🏁 Script executed:

cat apps/desktop/src/main/windows/main.ts | head -80

Repository: superset-sh/superset

Length of output: 2552


🏁 Script executed:

rg "const.*=.*persist" apps/desktop/src --type ts

Repository: superset-sh/superset

Length of output: 474


🏁 Script executed:

cat apps/desktop/src/shared/constants.ts | tail -30

Repository: superset-sh/superset

Length of output: 1093


🏁 Script executed:

rg "partition.*persist.*superset" apps/desktop/src --type ts -B 5 -A 5

Repository: superset-sh/superset

Length of output: 801


🏁 Script executed:

ast-grep --pattern 'webPreferences: { $$$partition: $_ $$$}'

Repository: superset-sh/superset

Length of output: 46


🏁 Script executed:

grep -n "persist:superset" apps/desktop/src/main/lib/sentry.ts

Repository: superset-sh/superset

Length of output: 113


🏁 Script executed:

grep -n "persist:superset" apps/desktop/src/main/windows/main.ts

Repository: superset-sh/superset

Length of output: 100


🏁 Script executed:

head -50 apps/desktop/src/main/lib/sentry.ts

Repository: superset-sh/superset

Length of output: 1224


Extract partition identifier to a shared constant.

The implementation correctly registers both default and custom sessions using getSessions with IPCMode.Classic, aligning with @sentry/electron v7.5.0 best practices. However, the hardcoded string "persist:superset" appears duplicated in both sentry.ts (line 31) and windows/main.ts (line 63).

Per coding guidelines, extract this to a named constant in apps/desktop/src/shared/constants.ts and import it in both files to maintain a single source of truth:

export const SUPERSET_PARTITION = "persist:superset";

Then use session.fromPartition(SUPERSET_PARTITION) here and partition: SUPERSET_PARTITION in windows/main.ts.

🤖 Prompt for AI Agents
In `@apps/desktop/src/main/lib/sentry.ts` around lines 12 - 33, Hardcoded
partition string "persist:superset" is duplicated; extract it as an exported
constant named SUPERSET_PARTITION in the shared constants module, then replace
the literal in initSentry's getSessions (session.fromPartition(...)) with
session.fromPartition(SUPERSET_PARTITION) and replace the literal used for the
BrowserWindow partition setting in the windows main module with
SUPERSET_PARTITION; add the appropriate import of SUPERSET_PARTITION where
initSentry (getSessions) and the window creation code reference the partition.

Fix `net::ERR_UNKNOWN_URL_SCHEME` errors for `sentry-ipc://` URLs by
switching from Protocol IPC mode to Classic IPC mode.

Changes:
- Add Sentry preload script import to enable Classic IPC mode
- Bundle @sentry/electron in preload (exclude from externalization)
- Use IPCMode.Classic to avoid protocol registration timing issues
- Add getSessions to include custom session partition
- Move initSentry() to after app.whenReady() since Classic mode allows this
@saddlepaddle
saddlepaddle merged commit f5f3d05 into main Jan 22, 2026
8 checks passed
@Kitenite
Kitenite deleted the sentry branch January 23, 2026 05:47

This branch was previously deployed

2 inactive (1 outdated) deployments
production — 35906c22 Deployed Jan 22, 2026 by saddlepaddle via build / Build - macOS (arm64) #64
preview — 0767d2ec Deployed Jan 22, 2026 by saddlepaddle via Deploy Docs #1042
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