Skip to content

[codex] Add opt-in Sentry error monitoring#36

Merged
stepandel merged 8 commits into
masterfrom
codex/sentry-telemetry
May 31, 2026
Merged

[codex] Add opt-in Sentry error monitoring#36
stepandel merged 8 commits into
masterfrom
codex/sentry-telemetry

Conversation

@stepandel

@stepandel stepandel commented May 31, 2026

Copy link
Copy Markdown
Owner

Summary

  • add Sentry Electron error monitoring for main and renderer processes
  • add an opt-in Settings > Privacy toggle backed by the IPC contract and settings store
  • scrub sensitive event data and document telemetry privacy rules

Validation

  • pnpm typecheck
  • pnpm lint
  • pnpm test
  • pnpm check
  • pnpm test:smoke

Summary by CodeRabbit

  • New Features

    • Error reporting is now enabled by default and can be toggled in Settings > Privacy
    • Added error boundary to gracefully handle application crashes
  • Documentation

    • Added telemetry and privacy documentation detailing data handling practices
  • Chores

    • Updated build configuration and dependencies to support error reporting
    • Added automated release workflow

@coderabbitai

coderabbitai Bot commented May 31, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 53c1db21-76b5-4bbe-bb21-f267c68cd10f

📥 Commits

Reviewing files that changed from the base of the PR and between 7db7529 and 77037ee.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (14)
  • .github/workflows/release.yml
  • README.md
  • docs/TELEMETRY.md
  • electron/error-monitoring.ts
  • electron/ipc-contract.ts
  • electron/main.ts
  • electron/preload.ts
  • electron/settings-store.ts
  • package.json
  • src/components/ErrorBoundary.tsx
  • src/error-monitoring.ts
  • src/vite-env.d.ts
  • src/windows/SettingsWindow.tsx
  • vite.config.js

📝 Walkthrough

Walkthrough

This PR implements comprehensive error reporting across the Electron app using Sentry. It adds persistent user-configurable error reporting settings, initializes Sentry on both main and renderer processes with data sanitization, bridges settings through IPC, adds a Privacy settings tab, and includes build configuration, release automation, and telemetry documentation.

Changes

Error Monitoring Infrastructure and UI

Layer / File(s) Summary
Settings Storage & IPC Contract
electron/settings-store.ts, electron/ipc-contract.ts, src/vite-env.d.ts
SettingsSchema adds errorReportingEnabled boolean (defaults to true). SettingsStore provides accessor methods. ElectronAPI.settings exposes get/set/subscribe IPC methods with a validator parser.
Main Process Sentry Initialization
electron/error-monitoring.ts, electron/main.ts
New error-monitoring module implements DSN/release config, sensitive data sanitizers for URLs/tokens/recursion, Sentry initialization with PII disabled and request/user fields stripped, and error capture helpers. App startup initializes monitoring from stored settings.
IPC Error Handling & State Management
electron/main.ts
Introduces reportIpcError helper to standardize error responses and report via Sentry. Refactors multiple IPC handler catch blocks to use centralized pattern. Adds settings:getErrorReportingEnabled and settings:setErrorReportingEnabled endpoints that validate, persist, apply state, and broadcast changes.
Preload IPC Bridge
electron/preload.ts
Imports Sentry preload and exposes error-reporting settings methods (getErrorReportingEnabled, setErrorReportingEnabled, onErrorReportingChange) to the renderer via contextBridge.
Renderer Monitoring & Error Boundary
src/error-monitoring.ts, src/components/ErrorBoundary.tsx, src/main.tsx
Renderer-side Sentry init reads settings from main process and auto-subscribes to changes; lazy initialization gated by flag and DSN availability. React ErrorBoundary captures render errors and reports them. App wraps component tree with boundary and initializes monitoring at startup.
Settings UI Privacy Tab
src/windows/SettingsWindow.tsx
Adds Privacy tab to settings window; extends tab system to validate and route to the new tab. Fetches error-reporting setting at load, renders checkbox with immediate optimistic persistence and error-handling fallback.
Build Configuration & Environment
vite.config.js, package.json
Derives Sentry DSN and release from env; injects them into main and renderer via vite.define. Conditionally enables hidden sourcemaps when Sentry is present. Adds @sentry/electron and @sentry/vite-plugin dependencies.
Release Workflow & Documentation
.github/workflows/release.yml, docs/TELEMETRY.md, README.md
New manual macOS release workflow with Sentry/Apple secrets. Comprehensive telemetry docs describe analytics vs. error-reporting split, build-time DSN requirements, runtime user control via Privacy settings, event scrubbing rules (URL/token redaction, request/user field removal), and source map upload process. README update points users to privacy settings and telemetry docs.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

A rabbit hops through error logs so clear,
Sentry guards secrets with a thoughtful cheer,
Sanitized data, no token in sight,
Privacy honored, settings held tight,
Error reporting done oh-so-right! 🐰✨

✨ 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 codex/sentry-telemetry

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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.

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

🧹 Nitpick comments (4)
src/windows/SettingsWindow.tsx (1)

23-23: ⚡ Quick win

Derive tab validation from TABS to avoid drift.

The valid-tab list is hardcoded inline in both getInitialTab (Line 23) and the onSwitchTab handler (Line 52). Adding/renaming a tab now requires editing three places (TABS, both inline checks), which is easy to get wrong.

♻️ Proposed helper derived from `TABS`
 const TABS: { id: TabId; label: string; icon: typeof KeyRound }[] = [
   { id: 'api-keys', label: 'API Keys', icon: KeyRound },
   { id: 'shortcuts', label: 'Shortcuts', icon: Keyboard },
   { id: 'appearance', label: 'Appearance', icon: Palette },
   { id: 'privacy', label: 'Privacy', icon: ShieldCheck },
 ]
+
+function isTabId(value: string | null): value is TabId {
+  return TABS.some(tab => tab.id === value)
+}
 function getInitialTab(): TabId {
   const params = new URLSearchParams(window.location.search)
   const tab = params.get('tab')
-  if (tab === 'shortcuts' || tab === 'api-keys' || tab === 'appearance' || tab === 'privacy') {
+  if (isTabId(tab)) {
     return tab
   }
   return 'api-keys'
 }
     const unsubscribe = window.electronAPI.settings.onSwitchTab((tab) => {
-      if (tab === 'shortcuts' || tab === 'api-keys' || tab === 'appearance' || tab === 'privacy') {
+      if (isTabId(tab)) {
         setActiveTab(tab)
       }
     })

Also applies to: 52-52

🤖 Prompt for 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.

In `@src/windows/SettingsWindow.tsx` at line 23, The tab-validation logic is
duplicated and hardcoded; update getInitialTab and the onSwitchTab handler to
derive the valid-tabs check from the existing TABS constant instead of the
inline string list. Replace the current conditional (tab === 'shortcuts' || ...)
with a membership test using TABS (e.g., TABS.includes(tab) or TABSSet.has(tab))
so both getInitialTab and onSwitchTab validate against the single source of
truth TABS and cannot drift when tabs are added/renamed.
src/components/ErrorBoundary.tsx (1)

17-21: ⚡ Quick win

Component stack is reduced too aggressively for effective debugging.

Line 19 reduces info.componentStack to just 'present' or 'missing' to avoid sending potentially sensitive data. However, the component stack shows the React component tree path and is critical for debugging React errors—it's not PII and won't contain user data. Meanwhile, the error object itself (line 18) is sent with its full stack trace, which would contain file paths and code context anyway.

Consider sending the actual componentStack value (or at least a sanitized/truncated version) to preserve debugging utility while the main-process sanitization (in electron/error-monitoring.ts) already handles sensitive data redaction.

📊 Proposed fix to preserve component stack
   componentDidCatch(error: Error, info: React.ErrorInfo): void {
     captureRendererError(error, {
-      componentStack: info.componentStack ? 'present' : 'missing',
+      hasComponentStack: info.componentStack ? 'true' : 'false',
     })
+    // Let Sentry capture the full error with componentStack for debugging
+    if (info.componentStack) {
+      console.error('Component stack:', info.componentStack)
+    }
   }

Or, to send the component stack as a tag (truncated):

   componentDidCatch(error: Error, info: React.ErrorInfo): void {
+    const context: Record<string, string> = {}
+    if (info.componentStack) {
+      // Truncate to first 500 chars to avoid huge payloads
+      context.componentStack = info.componentStack.slice(0, 500)
+    }
-    captureRendererError(error, {
-      componentStack: info.componentStack ? 'present' : 'missing',
-    })
+    captureRendererError(error, context)
   }
🤖 Prompt for 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.

In `@src/components/ErrorBoundary.tsx` around lines 17 - 21, The componentDidCatch
implementation currently replaces info.componentStack with 'present'/'missing',
stripping valuable debugging context; update componentDidCatch (the
ErrorBoundary method) to pass the real component stack to captureRendererError
instead — either pass info.componentStack directly or a sanitized/truncated
substring (e.g., first N chars or lines) before calling captureRendererError so
the component tree is preserved for debugging while relying on
electron/error-monitoring sanitization for PII; locate captureRendererError and
componentDidCatch in ErrorBoundary.tsx to apply this change.
electron/error-monitoring.ts (2)

18-40: ⚡ Quick win

Consider adding depth limit to recursive sanitization.

The sanitizeValue function recursively processes nested objects without a depth limit. While object breadth is capped at 30 entries (line 32) and arrays at 10 items (line 28), deeply nested structures could still cause stack overflow.

🛡️ Proposed fix to add depth guard
-function sanitizeValue(value: unknown, key = ''): unknown {
+function sanitizeValue(value: unknown, key = '', depth = 0): unknown {
+  if (depth > 20) {
+    return '[max-depth-exceeded]'
+  }
+
   if (SENSITIVE_KEY_PATTERN.test(key)) {
     return '[redacted]'
   }

   if (typeof value === 'string') {
     return redactString(value)
   }

   if (Array.isArray(value)) {
-    return value.slice(0, 10).map((item) => sanitizeValue(item))
+    return value.slice(0, 10).map((item) => sanitizeValue(item, '', depth + 1))
   }

   if (value && typeof value === 'object') {
     const entries = Object.entries(value as Record<string, unknown>).slice(0, 30)
     return Object.fromEntries(entries.map(([entryKey, entryValue]) => [
       entryKey,
-      sanitizeValue(entryValue, entryKey),
+      sanitizeValue(entryValue, entryKey, depth + 1),
     ]))
   }

   return value
 }
🤖 Prompt for 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.

In `@electron/error-monitoring.ts` around lines 18 - 40, The sanitizeValue
function can recurse indefinitely and needs a depth guard: add an optional depth
parameter (e.g., depth = 0) and a MAX_SANITIZE_DEPTH constant, check at the top
of sanitizeValue and return a safe placeholder (or the original primitive) when
depth >= MAX_SANITIZE_DEPTH to stop recursion; then increment depth when calling
sanitizeValue recursively for arrays and object entries (map items and
sanitizeValue(entryValue, entryKey, depth + 1)), and ensure
SENSITIVE_KEY_PATTERN and redactString usage remain unchanged.

23-25: 💤 Low value

String redaction may be too aggressive for non-sensitive values.

Every string value is passed through redactString (line 24), which truncates to 500 characters and redacts URLs/long tokens—even for non-sensitive strings like error messages or safe metadata. This could reduce the debugging value of error reports.

Consider applying redactString only when the key matches sensitive patterns, or skip truncation for error messages and stack traces that are inherently useful for debugging.

🤖 Prompt for 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.

In `@electron/error-monitoring.ts` around lines 23 - 25, The current branch always
passes every string through redactString (the if (typeof value === 'string')
return redactString(value) path), which over-redacts; change this to
conditionally redact based on the key and context: accept a key parameter (or
use the existing key variable) and only call redactString for sensitive keys
(match via a sensitivePatterns regex or list like
/password|token|secret|apiKey/i), while returning the original string for safe
keys such as "message", "error", "stack", or other metadata; for stack/message
still optionally sanitize long URLs but avoid truncating the whole string (i.e.,
use a lighter sanitizer or skip truncation), and update references to
redactString usage accordingly so non-sensitive error messages remain intact.
🤖 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.

Nitpick comments:
In `@electron/error-monitoring.ts`:
- Around line 18-40: The sanitizeValue function can recurse indefinitely and
needs a depth guard: add an optional depth parameter (e.g., depth = 0) and a
MAX_SANITIZE_DEPTH constant, check at the top of sanitizeValue and return a safe
placeholder (or the original primitive) when depth >= MAX_SANITIZE_DEPTH to stop
recursion; then increment depth when calling sanitizeValue recursively for
arrays and object entries (map items and sanitizeValue(entryValue, entryKey,
depth + 1)), and ensure SENSITIVE_KEY_PATTERN and redactString usage remain
unchanged.
- Around line 23-25: The current branch always passes every string through
redactString (the if (typeof value === 'string') return redactString(value)
path), which over-redacts; change this to conditionally redact based on the key
and context: accept a key parameter (or use the existing key variable) and only
call redactString for sensitive keys (match via a sensitivePatterns regex or
list like /password|token|secret|apiKey/i), while returning the original string
for safe keys such as "message", "error", "stack", or other metadata; for
stack/message still optionally sanitize long URLs but avoid truncating the whole
string (i.e., use a lighter sanitizer or skip truncation), and update references
to redactString usage accordingly so non-sensitive error messages remain intact.

In `@src/components/ErrorBoundary.tsx`:
- Around line 17-21: The componentDidCatch implementation currently replaces
info.componentStack with 'present'/'missing', stripping valuable debugging
context; update componentDidCatch (the ErrorBoundary method) to pass the real
component stack to captureRendererError instead — either pass
info.componentStack directly or a sanitized/truncated substring (e.g., first N
chars or lines) before calling captureRendererError so the component tree is
preserved for debugging while relying on electron/error-monitoring sanitization
for PII; locate captureRendererError and componentDidCatch in ErrorBoundary.tsx
to apply this change.

In `@src/windows/SettingsWindow.tsx`:
- Line 23: The tab-validation logic is duplicated and hardcoded; update
getInitialTab and the onSwitchTab handler to derive the valid-tabs check from
the existing TABS constant instead of the inline string list. Replace the
current conditional (tab === 'shortcuts' || ...) with a membership test using
TABS (e.g., TABS.includes(tab) or TABSSet.has(tab)) so both getInitialTab and
onSwitchTab validate against the single source of truth TABS and cannot drift
when tabs are added/renamed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dc23014d-dfda-4206-b652-1defabbe34d0

📥 Commits

Reviewing files that changed from the base of the PR and between ac64ae5 and 7db7529.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (15)
  • README.md
  • docs/TELEMETRY.md
  • electron/error-monitoring.ts
  • electron/ipc-contract.ts
  • electron/main.ts
  • electron/preload.ts
  • electron/settings-store.ts
  • package.json
  • src/components/ErrorBoundary.tsx
  • src/components/layout/DeveloperMessageDialog.tsx
  • src/components/layout/TopBar.tsx
  • src/error-monitoring.ts
  • src/main.tsx
  • src/windows/SettingsWindow.tsx
  • vite.config.js

@stepandel
stepandel marked this pull request as ready for review May 31, 2026 04:09
@stepandel
stepandel merged commit 5345e53 into master May 31, 2026
1 of 2 checks passed
@stepandel
stepandel deleted the codex/sentry-telemetry branch May 31, 2026 04:09
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