From d486ff91a28ea255e23fdbf75c609f981b661cc5 Mon Sep 17 00:00:00 2001 From: Haileyesus Date: Fri, 20 Feb 2026 11:18:40 +0300 Subject: [PATCH] Fix session processing state leaking across session switches Root cause: - Processing ownership was derived from UI view state in ChatInterface. - While switching sessions, an in-flight isLoading=true could stamp the newly selected session as processing, even if a different session was actually running. - session-status handling only promoted isProcessing=true and did not clear stale processing state on isProcessing=false, which could leave sessions blocked. Changes: - ChatInterface: removed implicit processing propagation effect that called onSessionProcessing(selectedSession?.id || currentSessionId) when isLoading was true. This decouples processing ownership from transient view/session transitions. - useChatComposerState: added onSessionProcessing callback usage and explicitly marks processing at submit time for the concrete effectiveSessionId (non-temporary IDs only). This ties processing to the session that actually started work. - useChatRealtimeHandlers: expanded session-status handling to support both states. - isProcessing=true: mark session as processing; set loading/abort only when it is the currently viewed session. - isProcessing=false: clear active+processing markers and clear loading indicators for the current session view. Behavioral outcome: - Running session A no longer blocks session B after navigation. - Users can work in multiple sessions concurrently, and processing badges/loading state stay session-scoped. Verification: - npm run typecheck - npm run build --- .../chat/hooks/useChatComposerState.ts | 6 ++++++ .../chat/hooks/useChatRealtimeHandlers.ts | 20 ++++++++++++++++--- src/components/chat/view/ChatInterface.tsx | 8 +------- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/components/chat/hooks/useChatComposerState.ts b/src/components/chat/hooks/useChatComposerState.ts index ca430a1..a779bcd 100644 --- a/src/components/chat/hooks/useChatComposerState.ts +++ b/src/components/chat/hooks/useChatComposerState.ts @@ -47,6 +47,7 @@ interface UseChatComposerStateArgs { sendMessage: (message: unknown) => void; sendByCtrlEnter?: boolean; onSessionActive?: (sessionId?: string | null) => void; + onSessionProcessing?: (sessionId?: string | null) => void; onInputFocusChange?: (focused: boolean) => void; onFileOpen?: (filePath: string, diffInfo?: unknown) => void; onShowSettings?: () => void; @@ -98,6 +99,7 @@ export function useChatComposerState({ sendMessage, sendByCtrlEnter, onSessionActive, + onSessionProcessing, onInputFocusChange, onFileOpen, onShowSettings, @@ -569,6 +571,9 @@ export function useChatComposerState({ pendingViewSessionRef.current = { sessionId: null, startedAt: Date.now() }; } onSessionActive?.(sessionToActivate); + if (effectiveSessionId && !isTemporarySessionId(effectiveSessionId)) { + onSessionProcessing?.(effectiveSessionId); + } const getToolsSettings = () => { try { @@ -666,6 +671,7 @@ export function useChatComposerState({ executeCommand, isLoading, onSessionActive, + onSessionProcessing, pendingViewSessionRef, permissionMode, provider, diff --git a/src/components/chat/hooks/useChatRealtimeHandlers.ts b/src/components/chat/hooks/useChatRealtimeHandlers.ts index e84f726..6ba11cc 100644 --- a/src/components/chat/hooks/useChatRealtimeHandlers.ts +++ b/src/components/chat/hooks/useChatRealtimeHandlers.ts @@ -956,12 +956,26 @@ export function useChatRealtimeHandlers({ case 'session-status': { const statusSessionId = latestMessage.sessionId; + if (!statusSessionId) { + break; + } + const isCurrentSession = statusSessionId === currentSessionId || (selectedSession && statusSessionId === selectedSession.id); - if (isCurrentSession && latestMessage.isProcessing) { - setIsLoading(true); - setCanAbortSession(true); + + if (latestMessage.isProcessing) { onSessionProcessing?.(statusSessionId); + if (isCurrentSession) { + setIsLoading(true); + setCanAbortSession(true); + } + break; + } + + onSessionInactive?.(statusSessionId); + onSessionNotProcessing?.(statusSessionId); + if (isCurrentSession) { + clearLoadingIndicators(); } break; } diff --git a/src/components/chat/view/ChatInterface.tsx b/src/components/chat/view/ChatInterface.tsx index 946a080..11091e5 100644 --- a/src/components/chat/view/ChatInterface.tsx +++ b/src/components/chat/view/ChatInterface.tsx @@ -180,6 +180,7 @@ function ChatInterface({ sendMessage, sendByCtrlEnter, onSessionActive, + onSessionProcessing, onInputFocusChange, onFileOpen, onShowSettings, @@ -238,13 +239,6 @@ function ChatInterface({ }; }, [canAbortSession, handleAbortSession, isLoading]); - useEffect(() => { - const processingSessionId = selectedSession?.id || currentSessionId; - if (processingSessionId && isLoading && onSessionProcessing) { - onSessionProcessing(processingSessionId); - } - }, [currentSessionId, isLoading, onSessionProcessing, selectedSession?.id]); - useEffect(() => { return () => { resetStreamingState();