From 0c6568a189c7d582725425172f2c31ddfe42e116 Mon Sep 17 00:00:00 2001 From: hugocasa Date: Mon, 31 Aug 2026 23:48:18 +0200 Subject: [PATCH] fix(ai-sessions): give the catch-up failure path the same supersession answer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The catch block was the one probe-less way to arm a retry: a catch-up whose loadPastChat threw after a newer turn-end queued would schedule a retry carrying its stale chat id, which could then supersede the newer catch-up and re-adopt — and re-persist — the chat the driver rotated away from. The catch now makes the same supersession check the 'unavailable' path already sits behind, so a superseded failure leaves the gate and the retry to the catch-up that owns them. Also makes the latestWins failure case actually fail: both run calls were synchronous, so the throwing task was skipped as superseded and the catch went unexercised. A tick between them lets it throw, and the suite now asserts the error was seen as well as the follow-up running. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_019N8x51GqABUEFvg52V6aYo --- frontend/src/lib/components/sessions/latestWins.test.ts | 4 ++++ .../src/lib/components/sessions/sessionRuntime.svelte.ts | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) 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)