[bug][ai-chat] New chat adopts the wrong chat id under a two-tab race → turns leak into another chat #137
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Severity: high (data integrity — turns persist into the wrong chat; the agent reads another chat's history)
Symptom
With two AI-chat tabs open, a message/command typed in one chat "leaks" into another chat: subsequent turns of tab A are saved into tab B's chat, and tab A's agent answers using tab B's history — while tab A's visible UI still shows its own conversation. ("Copy chat"/export and the agent see the wrong chat; the live UI shows the right one.)
Root cause (confirmed in code + reproduced live)
When a user starts a new chat (
activeChatId === null) and sends the first message, the server creates theai_chatsrow (ai-chat.service.ts, new-chat insert) but the SSE stream never returns the new chat id to the client (nomessageMetadata/data part/header carries it). The client therefore "adopts" the id by guessing the newest chat in the per-user list:ai-chat-window.tsx—onTurnFinishedarmsadoptNewChat, then the adopt effect takeschats?.items?.[0]as the id to bind the thread to.useAiChatsQueryis per-user, not per-tab; the server ordersai_chats.created_at desc(ai-chat.repo.ts), soitems[0]is whichever chat was created most recently across all the user's tabs.So if a second tab creates a new chat at ~the same time (its row is newer), the first tab adopts the other tab's chat id. From then on tab A's
chatIdpoints at tab B's chat: A's turns persist there and the agent rebuilds history from there.Reproduction (live, two browser contexts, same user)
items[0].)DB proof
A's chat row contains only A's first turn; A's second turn landed in B's chat row. The race window is the duration of tab A's first turn, so it triggers easily on a normal multi-tab workflow.
Suggested fix
Return the authoritative new chat id from the server and adopt that, instead of guessing
items[0]:chatIdon the stream for a new chat — e.g. via the AI SDKmessageMetadata/a data part, or anX-Chat-Idresponse header on/ai-chat/stream.onResponse(oronFinish), read that id andsetActiveChatId(returnedId)/ bind the thread to it; drop thechats.items[0]heuristic. This removes the per-user race entirely.Note (separate, environment)
On the latest
develop, AI chat returns HTTP 500 on every/api/ai-chat/chatsand/api/ai-chat/streamuntil migration20260622T120000-ai-chat-page-origin(addsai_chats.page_id) is applied —ai-chat.repo.ts findByCreatorselectspage_id/joinspages. Not a code bug, but it fully blocks AI chat on an un-migrated DB.Not reproduced (reported separately by the user, checked here)
"History partially missing on reload, appears on next reload" and "history wrong when opening a saved chat in another tab" did not reproduce in isolation on this stand (full history rendered on every reload and in a fresh context). However, the adoption leak above splits one logical conversation across two chat rows, which can present as "part of the history is missing / it's in the other chat".
Reproduced live on
develop@v0.93.0-99-g2d7f85fcwith z.ai (glm-5.2) streaming; DB-correlated.Ghost referenced this issue2026-06-23 01:18:04 +03:00
Ghost referenced this issue2026-06-23 02:03:26 +03:00
Ghost referenced this issue2026-06-23 02:26:29 +03:00
Ghost referenced this issue2026-06-23 02:56:06 +03:00
Ghost referenced this issue2026-06-23 03:22:04 +03:00
Ghost referenced this issue2026-06-24 12:45:05 +03:00