From be6eabce75b0417f72e103aa99af1bc4adb9e239 Mon Sep 17 00:00:00 2001 From: Lluis Agusti Date: Thu, 16 Apr 2026 17:11:31 +0700 Subject: [PATCH] fix(frontend/copilot): clear reconnect timeout on exhaustion, guard against stale fire, remove dead state - Clear reconnectTimeoutTimerRef when RECONNECT_MAX_ATTEMPTS exceeded to prevent a second conflicting toast from the 30s timeout - Guard timeout callback with sessionEpoch and isReconnectScheduled checks so it won't fire after the stream has already resumed - Remove unused reconnectTimedOut state variable (dead code) Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/app/(platform)/copilot/useCopilotStream.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/autogpt_platform/frontend/src/app/(platform)/copilot/useCopilotStream.ts b/autogpt_platform/frontend/src/app/(platform)/copilot/useCopilotStream.ts index 9c34109eb2..42ef50b250 100644 --- a/autogpt_platform/frontend/src/app/(platform)/copilot/useCopilotStream.ts +++ b/autogpt_platform/frontend/src/app/(platform)/copilot/useCopilotStream.ts @@ -132,14 +132,14 @@ export function useCopilotStream({ const reconnectStartedAtRef = useRef(null); // Timer for the forced reconnect timeout. const reconnectTimeoutTimerRef = useRef>(); - // Set when reconnecting has timed out (>30s). - const [reconnectTimedOut, setReconnectTimedOut] = useState(false); function handleReconnect(sid: string) { if (isReconnectScheduledRef.current || !sid) return; const nextAttempt = reconnectAttemptsRef.current + 1; if (nextAttempt > RECONNECT_MAX_ATTEMPTS) { + clearTimeout(reconnectTimeoutTimerRef.current); + reconnectTimeoutTimerRef.current = undefined; setReconnectExhausted(true); toast({ title: "Connection lost", @@ -155,9 +155,11 @@ export function useCopilotStream({ // Schedule a forced timeout — if reconnecting takes longer than // RECONNECT_MAX_DURATION_MS, force the UI back to idle. clearTimeout(reconnectTimeoutTimerRef.current); + const capturedEpoch = sessionEpochRef.current; reconnectTimeoutTimerRef.current = setTimeout(() => { + if (sessionEpochRef.current !== capturedEpoch) return; + if (!isReconnectScheduledRef.current) return; setReconnectExhausted(true); - setReconnectTimedOut(true); reconnectStartedAtRef.current = null; toast({ title: "Connection timed out", @@ -530,7 +532,6 @@ export function useCopilotStream({ hasShownDisconnectToast.current = false; lastSubmittedMsgRef.current = null; setReconnectExhausted(false); - setReconnectTimedOut(false); setIsSyncing(false); hasResumedRef.current.clear(); hydrateCompletedRef.current = false; @@ -565,7 +566,6 @@ export function useCopilotStream({ reconnectStartedAtRef.current = null; clearTimeout(reconnectTimeoutTimerRef.current); reconnectTimeoutTimerRef.current = undefined; - setReconnectTimedOut(false); // Intentionally NOT clearing lastSubmittedMsgRef here: keeping the last // submitted text prevents getSendSuppressionReason from allowing a // duplicate POST of the same message immediately after a successful turn @@ -651,7 +651,6 @@ export function useCopilotStream({ status, error: isReconnecting || isUserStoppingRef.current ? undefined : error, isReconnecting, - reconnectTimedOut, isSyncing, isUserStoppingRef, rateLimitMessage,