fix(desktop): match current assistant-ui lookup errors

This commit is contained in:
Jakub Wolniewicz 2026-07-28 14:18:30 +02:00 committed by bb
parent 0405c26645
commit bb9434d307
2 changed files with 16 additions and 12 deletions

View File

@ -3,7 +3,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { ErrorBoundary } from './error-boundary'
const TAP_LOOKUP_ERROR = new Error('tapClientLookup: Index 6 out of bounds (length: 2)')
const CURRENT_LOOKUP_ERROR = new Error('useClientLookup: Index 6 out of bounds (length: 2)')
const RELOAD_WINDOW = { name: 'Reload window', role: 'button' } as const
function makeBomb(box: { error: Error | null }) {
@ -17,9 +17,9 @@ function makeBomb(box: { error: Error | null }) {
}
const recoveryWarningCount = (calls: unknown[][]) =>
calls.filter(call => call.some(value => String(value).includes('auto-recovering from tapClientLookup'))).length
calls.filter(call => call.some(value => String(value).includes('auto-recovering from assistant-ui lookup'))).length
describe('ErrorBoundary tapClientLookup recovery', () => {
describe('ErrorBoundary assistant-ui lookup recovery', () => {
beforeEach(() => {
vi.useFakeTimers()
vi.spyOn(console, 'error').mockImplementation(() => undefined)
@ -31,9 +31,13 @@ describe('ErrorBoundary tapClientLookup recovery', () => {
vi.restoreAllMocks()
})
it('recovers the root boundary after a transient lookup race clears', () => {
it.each([
['the current useClientLookup error', CURRENT_LOOKUP_ERROR],
['the legacy tapClientLookup error', new Error('tapClientLookup: Index 6 out of bounds (length: 2)')],
['the legacy tapClientResource error', new Error('tapClientResource: Index 6 out of bounds (length: 2)')]
])('recovers the root boundary after %s clears', (_label, error) => {
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
const box: { error: Error | null } = { error: TAP_LOOKUP_ERROR }
const box: { error: Error | null } = { error }
const Bomb = makeBomb(box)
render(
@ -52,7 +56,7 @@ describe('ErrorBoundary tapClientLookup recovery', () => {
it('stops retrying a persistent lookup error after the recovery budget is exhausted', () => {
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
const box: { error: Error | null } = { error: TAP_LOOKUP_ERROR }
const box: { error: Error | null } = { error: CURRENT_LOOKUP_ERROR }
const Bomb = makeBomb(box)
render(
@ -72,7 +76,7 @@ describe('ErrorBoundary tapClientLookup recovery', () => {
it('does not auto-recover the same error in a scoped boundary', () => {
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined)
const Bomb = makeBomb({ error: TAP_LOOKUP_ERROR })
const Bomb = makeBomb({ error: CURRENT_LOOKUP_ERROR })
render(
<ErrorBoundary fallback={() => <div>scoped fallback</div>} label="thread">
@ -87,7 +91,7 @@ describe('ErrorBoundary tapClientLookup recovery', () => {
})
it.each([
['a renamed lookup error', new Error('useClientLookup: Index 6 out of bounds (length: 2)')],
['a non-bounds lookup error', new Error('useClientLookup: Key "missing" not found')],
['an unrelated render error', new Error('some unrelated application error')]
])('does not auto-recover %s at root', (_label, error) => {
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined)

View File

@ -23,11 +23,11 @@ interface ErrorBoundaryState {
// Some assistant-ui lookup races escape the message-local boundary and reach
// the root. Retry only that exact transient error class, never arbitrary render
// failures, and cap retries so a persistent failure still exposes the fallback.
const TAP_CLIENT_LOOKUP_ERROR = /^tapClientLookup: Index \d+\s+out of bounds \(length:\s*\d+\)$/i
const ASSISTANT_UI_LOOKUP_ERROR = /(useClientLookup|tapClient(Lookup|Resource)).*out of bounds/i
const MAX_AUTO_RECOVERIES = 3
const AUTO_RECOVERY_WINDOW_MS = 5_000
const isTransientTapClientLookupError = (error: Error): boolean => TAP_CLIENT_LOOKUP_ERROR.test(error.message)
const isTransientAssistantUiLookupError = (error: Error): boolean => ASSISTANT_UI_LOOKUP_ERROR.test(error.message)
export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
state: ErrorBoundaryState = { error: null }
@ -44,8 +44,8 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
console.error(tag, error, info.componentStack)
this.props.onError?.(error, info)
if (this.props.label === 'root' && isTransientTapClientLookupError(error) && this.takeAutoRecoveryAttempt()) {
console.warn(`${tag} auto-recovering from tapClientLookup render race`, error.message)
if (this.props.label === 'root' && isTransientAssistantUiLookupError(error) && this.takeAutoRecoveryAttempt()) {
console.warn(`${tag} auto-recovering from assistant-ui lookup render race`, error.message)
this.scheduleAutoRecovery()
}
}