diff --git a/frontend/src/lib/components/sessions/latestWins.test.ts b/frontend/src/lib/components/sessions/latestWins.test.ts index cf7686caf9..9ab3c81b95 100644 --- a/frontend/src/lib/components/sessions/latestWins.test.ts +++ b/frontend/src/lib/components/sessions/latestWins.test.ts @@ -72,10 +72,14 @@ describe('createLatestWins', () => { q.run('s', () => { throw new Error('boom') }) + // Let the throwing task actually run before the next one queues, or it + // is skipped as superseded and the failure never happens. + await tick() q.run('s', () => { ran.push('after') }) await tick() + expect(errorSpy).toHaveBeenCalled() expect(ran).toEqual(['after']) } finally { errorSpy.mockRestore() diff --git a/frontend/src/lib/components/sessions/sessionRuntime.svelte.ts b/frontend/src/lib/components/sessions/sessionRuntime.svelte.ts index 0e3afded1f..97857d15a4 100644 --- a/frontend/src/lib/components/sessions/sessionRuntime.svelte.ts +++ b/frontend/src/lib/components/sessions/sessionRuntime.svelte.ts @@ -1189,8 +1189,11 @@ async function applyTurnEnd( caughtUp = true } catch (e) { // A read that threw leaves the same mismatched pair an 'unavailable' one - // does, so it earns the same answer rather than a silent release. + // does, so it earns the same answer rather than a silent release — the + // supersession check included, or the retry would carry this task's stale + // chat id past the newer catch-up that owns the gate now. console.error('sessionRuntime: catch-up failed', e) + if (superseded()) return scheduleCatchUpRetry(sessionId, chatId, attempt) } finally { if (caughtUp) noteCaughtUp(sessionId)