diff --git a/src/main/crash-reporting/crash-report-store.test.ts b/src/main/crash-reporting/crash-report-store.test.ts index 1a14a9fa2e6..ed08a903f79 100644 --- a/src/main/crash-reporting/crash-report-store.test.ts +++ b/src/main/crash-reporting/crash-report-store.test.ts @@ -110,6 +110,21 @@ describe('CrashReportStore', () => { await expect(store.getLatestPending()).resolves.toBeNull() }) + it('never sweeps a React error-boundary report, even when it matches the crash event', async () => { + const { store } = await createStore() + // Same reason/exitCode as the process crash: the guard must not depend on + // the boundary reason literal staying distinct from Electron's exit reasons. + const boundary = await store.record({ ...input(), processType: 'react-render' }) + await store.record(input()) + + await store.markRendererCrashesAutoRecovered(Date.parse(boundary.createdAt) - 1_000) + + await expect(store.getLatestPending()).resolves.toMatchObject({ + id: boundary.id, + status: 'pending' + }) + }) + it('dismisses sibling pending records after one crash report is sent', async () => { const { store } = await createStore() await store.record(input()) diff --git a/src/main/crash-reporting/crash-report-store.ts b/src/main/crash-reporting/crash-report-store.ts index 99603af89ce..6ca3ff63f17 100644 --- a/src/main/crash-reporting/crash-report-store.ts +++ b/src/main/crash-reporting/crash-report-store.ts @@ -166,7 +166,13 @@ export class CrashReportStore { // healed crash still reaches the user, just wearing the sibling's report. const anchors = [...resolved] const sweptReports = nextReports.map((report) => { - if (report.status !== 'pending') { + // Why: relation matching ignores processType, so re-state the anchor + // pass's exclusion — a renderer-sourced report that is not the renderer + // process is the error boundary reporting itself, not a burst sibling. + if ( + report.status !== 'pending' || + (report.source === 'renderer' && report.processType !== 'renderer') + ) { return report } if (!anchors.some((anchor) => isRelatedCrashEvent(anchor, report))) { diff --git a/src/main/ipc/crash-reporting-recovered-renderer-prompt.test.ts b/src/main/ipc/crash-reporting-recovered-renderer-prompt.test.ts index 04b4540894d..b3454423ecc 100644 --- a/src/main/ipc/crash-reporting-recovered-renderer-prompt.test.ts +++ b/src/main/ipc/crash-reporting-recovered-renderer-prompt.test.ts @@ -199,7 +199,14 @@ describe('auto-recovered renderer crash reporting', () => { it('resolves the process crash without sweeping a React error-boundary crash beside it', async () => { const store = await createStore() registerCrashReportingHandlers(store) - const boundary = await store.record(reactErrorBoundaryCrash()) + // Why the crash-shaped reason/exitCode: relation matching ignores + // processType, so a boundary report only survives the sweep on its own + // guard — not on the reason literal happening not to collide. + const boundary = await store.record({ + ...reactErrorBoundaryCrash(), + reason: 'killed', + exitCode: 9 + }) await store.record(killedRendererCrash()) noteRendererRecoveryReloadIssued()