mirror of
https://github.com/stablyai/orca.git
synced 2026-10-20 16:03:20 +00:00
fix(crash-reporting): keep error-boundary reports out of the sibling sweep
Relation matching ignores processType, so a react-render report was swept the moment it shared reason and exitCode with the anchor. Unreachable today only because the boundary reason literal never collides with an Electron exit reason — an invariant held 200 lines away with nothing pinning it. Restates the anchor pass's own exclusion rather than reusing isReactErrorBoundaryReport(), which tests that same reason literal. Both regression tests now use a crash-shaped reason/exitCode so they fail without the guard instead of passing on the mismatch. Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -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())
|
||||
|
||||
@@ -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))) {
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user