fix: wake-turn steer delivered twice — claimSteers re-claimed the steer the pump's request already carried

The wake pump fires its turn with the steer INSIDE the request body but
dequeues the queue entry only after the turn returns 200 — so the entry
is still queued when the handler claims. The record doesn't carry the
steer yet (write-ahead persist hasn't run), so claimSteers claimed it a
SECOND time and the handler appended it: outgoing = [...body, ...claimed]
held two copies of the same deterministic steer id, and the write-ahead
persist saved that for the whole wake turn. The client's adoption poll
rendered both -> React duplicate-key warning (steer_ctx_<chatId>).
withMessageIds deduped only at onFinish, so the final record looked clean.

Fix: claimSteers(chatId, incoming) — a queued steer whose id is already in
the turn's incoming list is being delivered BY this very turn: finalized
and resolved, never appended again.
This commit is contained in:
amosblomqvist
2026-09-18 09:13:36 +03:00
parent 9324793a23
commit 597b3f43e1
3 changed files with 57 additions and 4 deletions
+4 -1
View File
@@ -129,7 +129,10 @@ export async function handleChatRequest(
// appended after the learner's answer (conversion-safe: the answer carries
// its output). Claiming happens after beginTurn, so this turn owns the
// registry and the write-ahead persist below makes the claim durable.
const steers = claimSteers(chatId);
// The incoming body is passed too: a pump-fired wake turn ALREADY CARRIES
// its steer, so claimSteers finalizes that entry instead of appending a
// second copy (duplicate-key bug, 2026-09-18).
const steers = claimSteers(chatId, body.messages as UIMessage[]);
const outgoing = (
steers.length > 0 ? [...(body.messages as UIMessage[]), ...steers] : body.messages
) as UIMessage[];