fix: prevent stale selectChat async results from clobbering new session task lane

Race condition: on page remount, selectChat(oldId) loads session data async.
If the user clicks New Chat before the API returns, the old session's
pending_task_lane was being applied to the new session's state, showing
stale tasks and blocking new ones from appearing.

Fix: currentChatRef tracks the most recently requested chat ID synchronously.
All chat-creation paths (selectChat, handleNewChat, handleResumeNew) update it
immediately. After each await in selectChat, bail if the ref no longer matches.

Also documents the pattern as Lesson 106 in CLAUDE.md for future reference.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-04-01 00:41:50 +00:00
parent af524ec99d
commit 5c11c1db33
2 changed files with 11 additions and 1 deletions

View File

@@ -81,6 +81,9 @@ export default function AssistantChatPage() {
const fileInputRef = useRef<HTMLInputElement>(null)
const dragCounterRef = useRef(0)
const prefillHandledRef = useRef(false)
// Tracks the most recently requested active chat ID so in-flight selectChat
// calls that complete after the user switches chats don't clobber new state.
const currentChatRef = useRef<string | null>(activeChatId)
// Persist active chat ID to sessionStorage
useEffect(() => {
@@ -214,6 +217,7 @@ export default function AssistantChatPage() {
}
const selectChat = useCallback(async (chatId: string) => {
currentChatRef.current = chatId
setActiveChatId(chatId)
// Clear TaskLane when switching chats — will restore from backend if available
setShowTaskLane(false)
@@ -221,6 +225,10 @@ export default function AssistantChatPage() {
setActiveActions([])
try {
const detail = await aiSessionsApi.getSession(chatId)
// Guard: if the user switched to a different chat while this API call was
// in flight (e.g. clicked "New Chat"), discard stale results so we don't
// clobber the new session's task lane state.
if (currentChatRef.current !== chatId) return
setMessages(
(detail.conversation_messages || []).map(m => ({
role: m.role as 'user' | 'assistant',
@@ -264,6 +272,7 @@ export default function AssistantChatPage() {
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
}
currentChatRef.current = session.session_id
setChats(prev => [chatItem, ...prev])
setActiveChatId(session.session_id)
setMessages([])
@@ -430,6 +439,7 @@ export default function AssistantChatPage() {
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
}
currentChatRef.current = session.session_id
setChats(prev => [chatItem, ...prev])
setActiveChatId(session.session_id)
setMessages([{ role: 'user', content: resumePrompt }])