fix(ai-sessions): give the catch-up failure path the same supersession answer

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019N8x51GqABUEFvg52V6aYo
This commit is contained in:
hugocasa
2026-08-31 23:48:18 +02:00
co-authored by Claude Fable 5
parent 9815c6c0a1
commit 0c6568a189
2 changed files with 8 additions and 1 deletions
@@ -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()
@@ -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)