@@ -4676,10 +4676,18 @@ export function ChatView({
46764676 const tabsToRender = useMemo ( ( ) => {
46774677 if ( ! activeSubChatId ) return [ ]
46784678
4679- // Use allSubChats from Zustand store for validation (not agentSubChats from tRPC)
4680- // allSubChats is updated optimistically when creating new sub-chats,
4681- // while agentSubChats from tRPC query may be stale during race conditions
4682- const validSubChatIds = new Set ( allSubChats . map ( sc => sc . id ) )
4679+ // Use agentSubChats from server (tRPC/remote API) as the authoritative source for validation.
4680+ // This fixes the race condition where:
4681+ // 1. setChatId resets allSubChats to [] but loads activeSubChatId from localStorage
4682+ // 2. tabsToRender was checking activeSubChatId against empty allSubChats → always failing
4683+ //
4684+ // agentSubChats comes from the server and is the "truth" about which sub-chats exist.
4685+ // allSubChats in Zustand is only populated AFTER the init useEffect runs.
4686+ //
4687+ // For optimistic updates when creating new sub-chats, we fall back to allSubChats
4688+ // since the new sub-chat won't be in agentSubChats yet (tRPC query is stale).
4689+ const sourceForValidation = agentSubChats . length > 0 ? agentSubChats : allSubChats
4690+ const validSubChatIds = new Set ( sourceForValidation . map ( sc => sc . id ) )
46834691
46844692 // If active sub-chat doesn't belong to this workspace → return []
46854693 // This prevents rendering sub-chats from another workspace during race condition
@@ -4711,9 +4719,15 @@ export function ChatView({
47114719 }
47124720 }
47134721
4714- // Return in validOpenIds order for consistent rendering
4715- return validOpenIds . filter ( id => mustRender . has ( id ) )
4716- } , [ activeSubChatId , pinnedSubChatIds , openSubChatIds , allSubChats ] )
4722+ // Return tabs to render
4723+ // Always include activeSubChatId even if not in validOpenIds (handles race condition
4724+ // where openSubChatIds from localStorage doesn't include the active tab yet)
4725+ const result = validOpenIds . filter ( id => mustRender . has ( id ) )
4726+ if ( ! result . includes ( activeSubChatId ) ) {
4727+ result . unshift ( activeSubChatId )
4728+ }
4729+ return result
4730+ } , [ activeSubChatId , pinnedSubChatIds , openSubChatIds , allSubChats , agentSubChats ] )
47174731
47184732 // Get PR status when PR exists (for checking if it's open/merged/closed)
47194733 const hasPrNumber = ! ! agentChat ?. prNumber
@@ -6414,15 +6428,16 @@ Make sure to preserve all functionality from both branches when resolving confli
64146428 < IconSpinner className = "h-6 w-6 animate-spin" />
64156429 </ div >
64166430 ) : (
6417- tabsToRender . map ( subChatId => {
6431+ tabsToRender . map ( subChatId => {
64186432 const chat = getOrCreateChat ( subChatId )
64196433 const isActive = subChatId === activeSubChatId
64206434 const isFirstSubChat = getFirstSubChatId ( agentSubChats ) === subChatId
64216435
64226436 // Defense in depth: double-check workspace ownership
6423- // Use allSubChats (Zustand) instead of agentSubChats (tRPC) because
6424- // new sub-chats are added to Zustand immediately but tRPC query may be stale
6425- const belongsToWorkspace = allSubChats . some ( sc => sc . id === subChatId )
6437+ // Use agentSubChats (server data) as primary source, fall back to allSubChats for optimistic updates
6438+ // This fixes the race condition where allSubChats is empty after setChatId but before setAllSubChats
6439+ const belongsToWorkspace = agentSubChats . some ( sc => sc . id === subChatId ) ||
6440+ allSubChats . some ( sc => sc . id === subChatId )
64266441
64276442 if ( ! chat || ! belongsToWorkspace ) return null
64286443
0 commit comments