mirror of
https://github.com/stablyai/orca.git
synced 2026-10-20 00:03:45 +00:00
fix(crash-reporting): disarm the recovery flag when auto-recovery gives up
The arm was set in one place and cancelled in none. When the renderer recovery circuit breaker opens, the arm from the last allowed attempt stays live for 30s, so the user's own reload from the "Orca keeps failing to load" dialog consumes it — and the whole crash-on-load loop is written dismissed with rendererAutoRecovered: true. That flag is a direct lie there: the same code path had just recorded renderer_recovery_circuit_breaker_open. It silences the highest-signal crash class we have, repeated launch-failed before bootstrap. Clearing on exhaustion leaves the manual retry unarmed, so the crash prompts on its own evidence at the next launch.
This commit is contained in:
@@ -13,6 +13,16 @@ export function noteRendererRecoveryReloadIssued(nowMs = Date.now()): void {
|
||||
recoveryReloadIssuedAtMs = nowMs
|
||||
}
|
||||
|
||||
/**
|
||||
* Disarm the outcome check because auto-recovery gave up.
|
||||
*
|
||||
* Why: the next bootstrap then comes from the user's own retry, and a crash the
|
||||
* breaker refused to keep reloading is the one they most need prompted about.
|
||||
*/
|
||||
export function clearRendererRecoveryReloadIssued(): void {
|
||||
recoveryReloadIssuedAtMs = null
|
||||
}
|
||||
|
||||
function takeRendererRecoveryReloadIssuedAt(nowMs: number): number | null {
|
||||
const issuedAtMs = recoveryReloadIssuedAtMs
|
||||
recoveryReloadIssuedAtMs = null
|
||||
@@ -55,5 +65,5 @@ export function resolveRecoveredRendererCrashReports(
|
||||
}
|
||||
|
||||
export function _resetRendererRecoveryOutcomeForTests(): void {
|
||||
recoveryReloadIssuedAtMs = null
|
||||
clearRendererRecoveryReloadIssued()
|
||||
}
|
||||
|
||||
+7
-1
@@ -281,7 +281,10 @@ import {
|
||||
type ExpectedTeardownScope
|
||||
} from './crash-reporting/process-gone-classification'
|
||||
import { recordProcessGoneCrash as recordProcessGoneCrashEvent } from './crash-reporting/process-gone-recorder'
|
||||
import { noteRendererRecoveryReloadIssued } from './crash-reporting/renderer-recovery-crash-outcome'
|
||||
import {
|
||||
clearRendererRecoveryReloadIssued,
|
||||
noteRendererRecoveryReloadIssued
|
||||
} from './crash-reporting/renderer-recovery-crash-outcome'
|
||||
import {
|
||||
advanceSyntheticTitleSpinnerEntries,
|
||||
type SyntheticTitleSpinnerEntry
|
||||
@@ -1228,6 +1231,9 @@ function openMainWindow(): BrowserWindow {
|
||||
expectedTeardown: getExpectedTeardownScope(webContentsId)
|
||||
}),
|
||||
onRendererRecoveryExhausted: ({ details, recentRecoveryCount }) => {
|
||||
// Why: the pending arm belongs to a reload that never brought the renderer
|
||||
// back, so the user's retry below must not resolve these as auto-recovered.
|
||||
clearRendererRecoveryReloadIssued()
|
||||
recordDurableCrashBreadcrumb('renderer_recovery_circuit_breaker_open', {
|
||||
reason: details.reason,
|
||||
exitCode: details.exitCode ?? null,
|
||||
|
||||
@@ -56,6 +56,7 @@ vi.mock('../observability/tracer', () => ({
|
||||
import { CrashReportStore } from '../crash-reporting/crash-report-store'
|
||||
import {
|
||||
_resetRendererRecoveryOutcomeForTests,
|
||||
clearRendererRecoveryReloadIssued,
|
||||
noteRendererRecoveryReloadIssued,
|
||||
resolveRecoveredRendererCrashReports
|
||||
} from '../crash-reporting/renderer-recovery-crash-outcome'
|
||||
@@ -259,6 +260,22 @@ describe('auto-recovered renderer crash reporting', () => {
|
||||
await expect(getLatestPending()).resolves.toMatchObject({ id: recorded.id, status: 'pending' })
|
||||
})
|
||||
|
||||
it('still prompts after the recovery breaker opened and the user reloaded by hand', async () => {
|
||||
const store = await createStore()
|
||||
registerCrashReportingHandlers(store)
|
||||
// The crash-on-load loop: every recovery reload dies before bootstrap.
|
||||
const recorded = await store.record({ ...killedRendererCrash(), reason: 'launch-failed' })
|
||||
|
||||
noteRendererRecoveryReloadIssued()
|
||||
// The breaker refuses the next attempt and Orca asks the user to retry.
|
||||
clearRendererRecoveryReloadIssued()
|
||||
// The user's own reload finally boots; it did not auto-recover anything.
|
||||
emitRendererBootstrapRendered()
|
||||
|
||||
await expect(getLatestPending()).resolves.toMatchObject({ id: recorded.id, status: 'pending' })
|
||||
expect((await getLatestReport())?.details.rendererAutoRecovered).toBeUndefined()
|
||||
})
|
||||
|
||||
it('leaves an unresolved crash from a previous session pending', async () => {
|
||||
const store = await createStore()
|
||||
registerCrashReportingHandlers(store)
|
||||
|
||||
Reference in New Issue
Block a user