Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/workflows/beta-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ jobs:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_TRACES_SAMPLE_RATE: ${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}
SENTRY_PROFILES_SAMPLE_RATE: ${{ secrets.SENTRY_PROFILES_SAMPLE_RATE }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}

- name: Package macOS (Intel)
run: |
Expand All @@ -119,6 +122,9 @@ jobs:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_TRACES_SAMPLE_RATE: ${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}
SENTRY_PROFILES_SAMPLE_RATE: ${{ secrets.SENTRY_PROFILES_SAMPLE_RATE }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}

- name: Submit notarization (async)
id: notarize
Expand Down Expand Up @@ -180,6 +186,9 @@ jobs:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_TRACES_SAMPLE_RATE: ${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}
SENTRY_PROFILES_SAMPLE_RATE: ${{ secrets.SENTRY_PROFILES_SAMPLE_RATE }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}

- name: Package macOS (Apple Silicon)
run: |
Expand All @@ -192,6 +201,9 @@ jobs:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_TRACES_SAMPLE_RATE: ${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}
SENTRY_PROFILES_SAMPLE_RATE: ${{ secrets.SENTRY_PROFILES_SAMPLE_RATE }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}

- name: Submit notarization (async)
id: notarize
Expand Down Expand Up @@ -255,6 +267,9 @@ jobs:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_TRACES_SAMPLE_RATE: ${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}
SENTRY_PROFILES_SAMPLE_RATE: ${{ secrets.SENTRY_PROFILES_SAMPLE_RATE }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}

- name: Package Windows
shell: bash
Expand All @@ -268,6 +283,9 @@ jobs:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_TRACES_SAMPLE_RATE: ${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}
SENTRY_PROFILES_SAMPLE_RATE: ${{ secrets.SENTRY_PROFILES_SAMPLE_RATE }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}

- name: Azure Login (OIDC)
if: env.AZURE_CLIENT_ID != ''
Expand Down Expand Up @@ -436,6 +454,9 @@ jobs:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_TRACES_SAMPLE_RATE: ${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}
SENTRY_PROFILES_SAMPLE_RATE: ${{ secrets.SENTRY_PROFILES_SAMPLE_RATE }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}

- name: Package Linux
run: |
Expand All @@ -446,6 +467,9 @@ jobs:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_TRACES_SAMPLE_RATE: ${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}
SENTRY_PROFILES_SAMPLE_RATE: ${{ secrets.SENTRY_PROFILES_SAMPLE_RATE }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}

- name: Verify Linux packages
run: cd apps/frontend && npm run verify:linux
Expand Down
24 changes: 24 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ jobs:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_TRACES_SAMPLE_RATE: ${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}
SENTRY_PROFILES_SAMPLE_RATE: ${{ secrets.SENTRY_PROFILES_SAMPLE_RATE }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}

- name: Package macOS (Intel)
run: cd apps/frontend && npm run package:mac -- --x64
Expand All @@ -72,6 +75,9 @@ jobs:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_TRACES_SAMPLE_RATE: ${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}
SENTRY_PROFILES_SAMPLE_RATE: ${{ secrets.SENTRY_PROFILES_SAMPLE_RATE }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
Comment on lines +78 to +80
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Sentry credentials in the packaging steps are unused and redundant.

SENTRY_AUTH_TOKEN, SENTRY_ORG, and SENTRY_PROJECT are consumed only during the Vite build step (npm run build) by the @sentry/vite-plugin. The subsequent packaging steps (npm run package:*) invoke electron-builder, which does not call Vite and therefore never reads these variables. Including them in the package steps is harmless but adds noise.

♻️ Remove redundant Sentry env vars from package steps
 - name: Package macOS (Intel)
   run: cd apps/frontend && npm run package:mac -- --x64
   env:
     GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
     CSC_LINK: ${{ secrets.MAC_CERTIFICATE }}
     CSC_KEY_PASSWORD: ${{ secrets.MAC_CERTIFICATE_PASSWORD }}
     SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
     SENTRY_TRACES_SAMPLE_RATE: ${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}
     SENTRY_PROFILES_SAMPLE_RATE: ${{ secrets.SENTRY_PROFILES_SAMPLE_RATE }}
-    SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
-    SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
-    SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}

Apply the same removal to the Package macOS (ARM64), Package Windows, and Package Linux steps.

Also applies to: 152-154, 228-230, 406-408

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

In @.github/workflows/release.yml around lines 78 - 80, Remove the redundant
Sentry environment variables (SENTRY_AUTH_TOKEN, SENTRY_ORG, SENTRY_PROJECT)
from the packaging workflow steps so they are only present for the Vite build
step; specifically edit the release.yml packaging jobs named "Package macOS
(ARM64)", "Package Windows", and "Package Linux" (and any other package:* steps)
to drop those three env entries under their env: blocks, leaving Sentry vars
only where the Vite build (npm run build) runs and consumes `@sentry/vite-plugin`.


- name: Submit notarization (async)
id: notarize
Expand Down Expand Up @@ -130,6 +136,9 @@ jobs:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_TRACES_SAMPLE_RATE: ${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}
SENTRY_PROFILES_SAMPLE_RATE: ${{ secrets.SENTRY_PROFILES_SAMPLE_RATE }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}

- name: Package macOS (Apple Silicon)
run: cd apps/frontend && npm run package:mac -- --arm64
Expand All @@ -140,6 +149,9 @@ jobs:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_TRACES_SAMPLE_RATE: ${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}
SENTRY_PROFILES_SAMPLE_RATE: ${{ secrets.SENTRY_PROFILES_SAMPLE_RATE }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}

- name: Submit notarization (async)
id: notarize
Expand Down Expand Up @@ -200,6 +212,9 @@ jobs:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_TRACES_SAMPLE_RATE: ${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}
SENTRY_PROFILES_SAMPLE_RATE: ${{ secrets.SENTRY_PROFILES_SAMPLE_RATE }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}

- name: Package Windows
run: cd apps/frontend && npm run package:win
Expand All @@ -210,6 +225,9 @@ jobs:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_TRACES_SAMPLE_RATE: ${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}
SENTRY_PROFILES_SAMPLE_RATE: ${{ secrets.SENTRY_PROFILES_SAMPLE_RATE }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}

- name: Azure Login (OIDC)
if: env.AZURE_CLIENT_ID != ''
Expand Down Expand Up @@ -374,6 +392,9 @@ jobs:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_TRACES_SAMPLE_RATE: ${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}
SENTRY_PROFILES_SAMPLE_RATE: ${{ secrets.SENTRY_PROFILES_SAMPLE_RATE }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}

- name: Package Linux
run: cd apps/frontend && npm run package:linux
Expand All @@ -382,6 +403,9 @@ jobs:
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
SENTRY_TRACES_SAMPLE_RATE: ${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}
SENTRY_PROFILES_SAMPLE_RATE: ${{ secrets.SENTRY_PROFILES_SAMPLE_RATE }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}

- name: Verify Linux packages
run: cd apps/frontend && npm run verify:linux
Expand Down
94 changes: 65 additions & 29 deletions apps/frontend/electron.vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { defineConfig, externalizeDepsPlugin } from 'electron-vite';
import react from '@vitejs/plugin-react';
import { sentryVitePlugin } from '@sentry/vite-plugin';
import { resolve } from 'path';
import { config as dotenvConfig } from 'dotenv';

Expand All @@ -21,38 +22,72 @@ const sentryDefines = {
'__SENTRY_PROFILES_SAMPLE_RATE__': JSON.stringify(process.env.SENTRY_PROFILES_SAMPLE_RATE || '0.1'),
};

/**
* Sentry source map upload configuration.
*
* Uploads source maps during CI builds so production stack traces are readable.
* Only activates when SENTRY_AUTH_TOKEN is set (CI builds via GitHub secrets).
* Source maps are NOT shipped to users — they're uploaded to Sentry then stripped.
*/
const hasSentryUploadConfig = !!(
process.env.SENTRY_AUTH_TOKEN &&
process.env.SENTRY_ORG &&
process.env.SENTRY_PROJECT
);

function createSentryPlugin() {
if (!hasSentryUploadConfig) {
return [];
}
return [
sentryVitePlugin({
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
authToken: process.env.SENTRY_AUTH_TOKEN,
sourcemaps: {
filesToDeleteAfterUpload: ['**/*.js.map'],
},
telemetry: false,
}),
];
}
Comment on lines +42 to +60
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

For improved type safety, consider using non-null assertions (!) for the environment variables within sentryVitePlugin. The hasSentryUploadConfig check already ensures these variables are defined, so asserting their existence makes the code more robust and communicates this guarantee to the TypeScript compiler.

Suggested change
function createSentryPlugin() {
if (!hasSentryUploadConfig) {
return [];
}
return [
sentryVitePlugin({
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
authToken: process.env.SENTRY_AUTH_TOKEN,
sourcemaps: {
filesToDeleteAfterUpload: ['**/*.js.map'],
},
telemetry: false,
}),
];
}
function createSentryPlugin() {
if (!hasSentryUploadConfig) {
return [];
}
return [
sentryVitePlugin({
org: process.env.SENTRY_ORG!,
project: process.env.SENTRY_PROJECT!,
authToken: process.env.SENTRY_AUTH_TOKEN!,
sourcemaps: {
filesToDeleteAfterUpload: ['**/*.js.map'],
},
telemetry: false,
}),
];
}

Comment on lines +42 to +60
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Release name mismatch between the Sentry plugin and Sentry.init().

The plugin has no release.name set, so it defaults to automatically detecting a value for your environment, and otherwise uses the git HEAD's commit SHA. Meanwhile, sentry.ts calls Sentry.init({ release: \auto-claude@${app.getVersion()}` })`.

The plugin injects release information into the build for the SDK to pick up when sending events (defaults to true). Because Sentry.init() explicitly overrides that injected release, events will be tagged with auto-claude@X.Y.Z, while source maps are uploaded under the auto-detected git SHA release. With the modern debug-ID approach, source map resolution still works correctly. However, Sentry's release tracking features (deploy tracking, release health, which errors appeared in which version) will show a mismatch.

Consider either:

  1. Reading the version from package.json at build time and passing it as release: { name: \auto-claude@${version}` }insentryVitePlugin`.
  2. Removing the explicit release: from Sentry.init() and letting the plugin-injected value propagate through.
♻️ Option 1 — align plugin release with SDK release
+import { readFileSync } from 'fs';
+const { version } = JSON.parse(readFileSync(resolve(__dirname, 'package.json'), 'utf8'));

 function createSentryPlugin() {
   if (!hasSentryUploadConfig) {
     return [];
   }
   return [
     sentryVitePlugin({
       org: process.env.SENTRY_ORG,
       project: process.env.SENTRY_PROJECT,
       authToken: process.env.SENTRY_AUTH_TOKEN,
+      release: {
+        name: `auto-claude@${version}`,
+      },
       sourcemaps: {
         filesToDeleteAfterUpload: ['**/*.js.map'],
       },
       telemetry: false,
     }),
   ];
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/frontend/electron.vite.config.ts` around lines 38 - 53,
createSentryPlugin currently doesn't set a release so the uploaded sourcemaps
use a different release than the runtime SDK (Sentry.init). Fix by reading the
app version at build time and passing it to sentryVitePlugin as release.name
(e.g. in createSentryPlugin read package.json version and set release: { name:
`auto-claude@${version}` }) so the plugin and SDK match; alternatively, remove
the explicit release: ... from Sentry.init so the plugin-injected release is
used at runtime. Reference: createSentryPlugin, sentryVitePlugin, Sentry.init,
app.getVersion.

⚠️ Potential issue | 🟠 Major

Missing errorHandler — upload failures will abort the CI release build.

By default, when an error occurs during release creation or sourcemaps upload, the plugin will simply throw an error, thereby stopping the bundling process. If an errorHandler callback is provided, compilation will continue, unless an error is thrown in the provided callback.

If SENTRY_AUTH_TOKEN is set (enabling the plugin via hasSentryUploadConfig) but has insufficient permissions, or if Sentry's ingest endpoint is unreachable during CI, every release build will fail. This could block production releases on transient infrastructure issues. Add an errorHandler to demote upload failures to build warnings while still surfacing the problem.

🛡️ Proposed fix — degrade upload failure to warning, not build halt
 function createSentryPlugin() {
   if (!hasSentryUploadConfig) {
     return [];
   }
   return [
     sentryVitePlugin({
       org: process.env.SENTRY_ORG,
       project: process.env.SENTRY_PROJECT,
       authToken: process.env.SENTRY_AUTH_TOKEN,
       sourcemaps: {
         filesToDeleteAfterUpload: ['**/*.js.map'],
       },
       telemetry: false,
+      errorHandler: (err) => {
+        console.warn('[Sentry] Source map upload failed:', err.message);
+      },
     }),
   ];
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function createSentryPlugin() {
if (!hasSentryUploadConfig) {
return [];
}
return [
sentryVitePlugin({
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
authToken: process.env.SENTRY_AUTH_TOKEN,
sourcemaps: {
filesToDeleteAfterUpload: ['**/*.js.map'],
},
telemetry: false,
}),
];
}
function createSentryPlugin() {
if (!hasSentryUploadConfig) {
return [];
}
return [
sentryVitePlugin({
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
authToken: process.env.SENTRY_AUTH_TOKEN,
sourcemaps: {
filesToDeleteAfterUpload: ['**/*.js.map'],
},
telemetry: false,
errorHandler: (err) => {
console.warn('[Sentry] Source map upload failed:', err.message);
},
}),
];
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/frontend/electron.vite.config.ts` around lines 38 - 53, The
createSentryPlugin function currently returns sentryVitePlugin(...) without an
errorHandler, so upload/release failures will abort the build; modify the
sentryVitePlugin call in createSentryPlugin to include an errorHandler callback
that receives the error and uses the existing logger (or console.warn) to emit a
non-fatal warning message (including error details) instead of rethrowing,
ensuring compilation continues when sourcemap/upload fails; keep all other
options (org, project, authToken, sourcemaps, telemetry) intact.


export default defineConfig({
main: {
define: sentryDefines,
plugins: [externalizeDepsPlugin({
// Bundle these packages into the main process (they won't be in node_modules in packaged app)
exclude: [
'uuid',
'chokidar',
'dotenv',
'electron-log',
'proper-lockfile',
'semver',
'zod',
'@anthropic-ai/sdk',
'kuzu',
'electron-updater',
'@electron-toolkit/utils',
// Sentry and its transitive dependencies (opentelemetry -> debug -> ms)
'@sentry/electron',
'@sentry/core',
'@sentry/node',
'@sentry/utils',
'@opentelemetry/instrumentation',
'debug',
'ms',
// Minimatch for glob pattern matching in worktree handlers
'minimatch',
// XState for task state machine
'xstate'
]
})],
plugins: [
externalizeDepsPlugin({
// Bundle these packages into the main process (they won't be in node_modules in packaged app)
exclude: [
'uuid',
'chokidar',
'dotenv',
'electron-log',
'proper-lockfile',
'semver',
'zod',
'@anthropic-ai/sdk',
'kuzu',
'electron-updater',
'@electron-toolkit/utils',
// Sentry and its transitive dependencies (opentelemetry -> debug -> ms)
'@sentry/electron',
'@sentry/core',
'@sentry/node',
'@sentry/utils',
'@opentelemetry/instrumentation',
'debug',
'ms',
// Minimatch for glob pattern matching in worktree handlers
'minimatch',
// XState for task state machine
'xstate'
]
}),
...createSentryPlugin(),
],
build: {
sourcemap: true,
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Prefer sourcemap: 'hidden' over sourcemap: true per Sentry's official guidance.

Sentry's Vite documentation uses sourcemap: "hidden" for source map generation to avoid embedding //# sourceMappingURL= comments in the output bundles. With sourcemap: true, those comments are written into the deployed JS files. After filesToDeleteAfterUpload removes the .js.map files, the embedded URL references point to non-existent paths — harmless in packaged Electron apps but inconsistent with Sentry's recommended setup.

♻️ Switch to hidden sourcemaps in both main and renderer
   main: {
     // ...
     build: {
-      sourcemap: true,
+      sourcemap: 'hidden',
   renderer: {
     // ...
     build: {
-      sourcemap: true,
+      sourcemap: 'hidden',

Also applies to: 114-114

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

In `@apps/frontend/electron.vite.config.ts` at line 90, Replace the Vite build
option sourcemap: true with sourcemap: 'hidden' so Sentry-compatible hidden
source maps are produced (avoid embedding //# sourceMappingURL= in output).
Update both occurrences of the sourcemap setting in this file (the renderer and
main build configs where sourcemap: true appears) to use sourcemap: 'hidden'
instead of true; this keeps .js.map files for upload but prevents emitted
sourceMappingURL comments after filesToDeleteAfterUpload removes the maps.

rollupOptions: {
input: {
index: resolve(__dirname, 'src/main/index.ts')
Expand All @@ -76,13 +111,14 @@ export default defineConfig({
define: sentryDefines,
root: resolve(__dirname, 'src/renderer'),
build: {
sourcemap: true,
rollupOptions: {
input: {
index: resolve(__dirname, 'src/renderer/index.html')
}
}
},
plugins: [react()],
plugins: [react(), ...createSentryPlugin()],
resolve: {
alias: {
'@': resolve(__dirname, 'src/renderer'),
Expand Down
1 change: 1 addition & 0 deletions apps/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@
"@electron-toolkit/utils": "^4.0.0",
"@electron/rebuild": "^4.0.2",
"@playwright/test": "^1.52.0",
"@sentry/vite-plugin": "^4.9.1",
"@tailwindcss/postcss": "^4.1.17",
"@testing-library/dom": "^10.0.0",
"@testing-library/jest-dom": "^6.9.1",
Expand Down
81 changes: 81 additions & 0 deletions apps/frontend/src/main/__tests__/sentry.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/**
* Tests for Sentry utility functions
*/

import { describe, it, expect, vi, beforeEach } from 'vitest';
import type { IpcMainInvokeEvent } from 'electron';

const mockCaptureException = vi.fn();

vi.mock('@sentry/electron/main', () => ({
init: vi.fn(),
addBreadcrumb: vi.fn(),
captureException: mockCaptureException,
}));

vi.mock('electron', () => ({
app: { isPackaged: false, getVersion: () => '0.0.0-test' },
ipcMain: { on: vi.fn(), handle: vi.fn() },
crashReporter: { start: vi.fn() },
}));

vi.mock('../settings-utils', () => ({
readSettingsFile: () => ({}),
}));

vi.mock('../../shared/constants', () => ({
DEFAULT_APP_SETTINGS: { sentryEnabled: true },
}));

vi.mock('../../shared/constants/ipc', () => ({
IPC_CHANNELS: {
SENTRY_STATE_CHANGED: 'sentry-state-changed',
GET_SENTRY_DSN: 'get-sentry-dsn',
GET_SENTRY_CONFIG: 'get-sentry-config',
},
}));

vi.mock('../../shared/utils/sentry-privacy', () => ({
processEvent: vi.fn((e) => e),
PRODUCTION_TRACE_SAMPLE_RATE: 0.1,
}));

describe('withSentryIpc', () => {
let withSentryIpc: typeof import('../sentry').withSentryIpc;
const fakeEvent = {} as IpcMainInvokeEvent;

beforeEach(async () => {
vi.clearAllMocks();
const mod = await import('../sentry');
withSentryIpc = mod.withSentryIpc;
});
Comment on lines +47 to +51
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Module caching between beforeEach calls may cause cross-test state bleed.

await import('../sentry') on the second and subsequent test runs returns the cached module — vi.clearAllMocks() clears mock call history but does not re-execute module-level code. Module-level state (sentryEnabledState, cachedDsn, etc.) persists across tests. The three existing tests are unaffected because withSentryIpc is a stateless factory, but any future test that exercises initSentryMain() or modifies module state will see stale values from a prior test.

♻️ Use `vi.resetModules()` for a clean slate per test
   beforeEach(async () => {
     vi.clearAllMocks();
+    vi.resetModules();
     const mod = await import('../sentry');
     withSentryIpc = mod.withSentryIpc;
   });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
beforeEach(async () => {
vi.clearAllMocks();
const mod = await import('../sentry');
withSentryIpc = mod.withSentryIpc;
});
beforeEach(async () => {
vi.clearAllMocks();
vi.resetModules();
const mod = await import('../sentry');
withSentryIpc = mod.withSentryIpc;
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/frontend/src/main/__tests__/sentry.test.ts` around lines 47 - 51, Tests
import '../sentry' in beforeEach but rely on module-level state which can
persist between tests; call vi.resetModules() before the dynamic import in the
beforeEach to clear Node's module cache so that functions/vars like
withSentryIpc, initSentryMain, sentryEnabledState and cachedDsn are
reinitialized for each test run.


it('passes through successful handler result', async () => {
const handler = vi.fn().mockResolvedValue('ok');
const wrapped = withSentryIpc('test-channel', handler);
const result = await wrapped(fakeEvent, 'arg1');
expect(result).toBe('ok');
expect(handler).toHaveBeenCalledWith(fakeEvent, 'arg1');
});

it('re-throws and captures exception on handler error', async () => {
const err = new Error('boom');
const handler = vi.fn().mockRejectedValue(err);
const wrapped = withSentryIpc('test-channel', handler);
await expect(wrapped(fakeEvent)).rejects.toThrow('boom');
expect(mockCaptureException).toHaveBeenCalledWith(
err,
{ tags: { ipc_channel: 'test-channel' } }
);
});

it('tags error with the correct ipc_channel', async () => {
const handler = vi.fn().mockRejectedValue(new Error('fail'));
const wrapped = withSentryIpc('my-custom-channel', handler);
await expect(wrapped(fakeEvent)).rejects.toThrow('fail');
expect(mockCaptureException).toHaveBeenCalledWith(
expect.any(Error),
{ tags: { ipc_channel: 'my-custom-channel' } }
);
});
});
Comment on lines +1 to +81
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

No test coverage for the new crashReporter.start() code path.

The crash reporter initialization added in initSentryMain() (lines 144–167 of sentry.ts) is not exercised by any test. The crashReporter.start mock is registered but never asserted upon. Given this involves DSN URL parsing and a conditional start call, consider adding a test that calls initSentryMain() with a valid DSN and verifies:

  • crashReporter.start is called with the correct submitURL
  • crashReporter.start is not called when Sentry is disabled or DSN is absent

Would you like me to draft the initSentryMain + crashReporter test cases?

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

In `@apps/frontend/src/main/__tests__/sentry.test.ts` around lines 1 - 81, Add
tests for initSentryMain to cover the crashReporter.start code path: import
initSentryMain from '../sentry' (or the exported name used) after setting up
mocks, then (1) mock DEFAULT_APP_SETTINGS to enable sentry and mock
readSettingsFile or the DSN provider to return a valid DSN string, call
initSentryMain(), and assert crashReporter.start was called once with an object
whose submitURL matches the DSN-derived URL you expect; (2) mock sentry disabled
(DEFAULT_APP_SETTINGS.sentryEnabled = false) or return no DSN, call
initSentryMain(), and assert crashReporter.start was not called; ensure you
clear mocks between cases and reference initSentryMain and crashReporter.start
in your assertions.

Loading
Loading