fix(dashboard): retry stalled events feed reconnects - #81931
Conversation
|
Superseded by #81978. Your diagnosis was right and the fix is carried there as the first commit, unchanged — the deadline around ticket minting plus the handshake, the generation guard against a late ticket opening a superseded socket, and all three tests. The reason for the salvage rather than a straight merge: the bug class is "the You're credited via |
…supersedes #81931) (#81978) * fix(dashboard): retry stalled events feed reconnects * fix(dashboard): bound the PTY ticket request before the socket exists ChatPage's connect awaits a single-use ticket from `api.buildWsUrl()` before `new WebSocket()`. That request produces no socket, so a rejection or a hang emits no `close` event and never arms PTY_CONNECTING_TIMEOUT_MS (set after the socket is constructed). The tab stranded on "connecting" with `connectInFlightRef` stuck true, which also suppresses the page-resume reconnect path. Give the ticket phase its own deadline and route both failure modes into the existing backoff. A `ticketSuperseded` flag invalidates a late ticket result so a timed-out attempt cannot open a socket behind the replacement it scheduled, and cleanup clears the timer on unmount. `scheduleReconnect` now takes `number | null` so an attempt that died before any socket existed omits the "(code N)" banner suffix instead of inventing one. Same bug class as the events-feed fix in the preceding commit, on the main chat surface. Co-authored-by: Gille <4317663+helix4u@users.noreply.github.com> * test(dashboard): cover the PTY ticket connect deadline Mirrors the events-feed cases in ChatSidebar.test.tsx: a rejected ticket retries, a stalled ticket times out and its late resolution cannot open a superseded socket, and a settled ticket disarms the deadline so PTY_CONNECTING_TIMEOUT_MS remains the only guard on a wedged handshake (NS-591 regression). Both failure cases fail against ChatPage.tsx without the preceding fix. Co-authored-by: Gille <4317663+helix4u@users.noreply.github.com> --------- Co-authored-by: Gille <4317663+helix4u@users.noreply.github.com>
…supersedes NousResearch#81931) (NousResearch#81978) * fix(dashboard): retry stalled events feed reconnects * fix(dashboard): bound the PTY ticket request before the socket exists ChatPage's connect awaits a single-use ticket from `api.buildWsUrl()` before `new WebSocket()`. That request produces no socket, so a rejection or a hang emits no `close` event and never arms PTY_CONNECTING_TIMEOUT_MS (set after the socket is constructed). The tab stranded on "connecting" with `connectInFlightRef` stuck true, which also suppresses the page-resume reconnect path. Give the ticket phase its own deadline and route both failure modes into the existing backoff. A `ticketSuperseded` flag invalidates a late ticket result so a timed-out attempt cannot open a socket behind the replacement it scheduled, and cleanup clears the timer on unmount. `scheduleReconnect` now takes `number | null` so an attempt that died before any socket existed omits the "(code N)" banner suffix instead of inventing one. Same bug class as the events-feed fix in the preceding commit, on the main chat surface. Co-authored-by: Gille <4317663+helix4u@users.noreply.github.com> * test(dashboard): cover the PTY ticket connect deadline Mirrors the events-feed cases in ChatSidebar.test.tsx: a rejected ticket retries, a stalled ticket times out and its late resolution cannot open a superseded socket, and a settled ticket disarms the deadline so PTY_CONNECTING_TIMEOUT_MS remains the only guard on a wedged handshake (NS-591 regression). Both failure cases fail against ChatPage.tsx without the preceding fix. Co-authored-by: Gille <4317663+helix4u@users.noreply.github.com> --------- Co-authored-by: Gille <4317663+helix4u@users.noreply.github.com>
…supersedes NousResearch#81931) (NousResearch#81978) * fix(dashboard): retry stalled events feed reconnects * fix(dashboard): bound the PTY ticket request before the socket exists ChatPage's connect awaits a single-use ticket from `api.buildWsUrl()` before `new WebSocket()`. That request produces no socket, so a rejection or a hang emits no `close` event and never arms PTY_CONNECTING_TIMEOUT_MS (set after the socket is constructed). The tab stranded on "connecting" with `connectInFlightRef` stuck true, which also suppresses the page-resume reconnect path. Give the ticket phase its own deadline and route both failure modes into the existing backoff. A `ticketSuperseded` flag invalidates a late ticket result so a timed-out attempt cannot open a socket behind the replacement it scheduled, and cleanup clears the timer on unmount. `scheduleReconnect` now takes `number | null` so an attempt that died before any socket existed omits the "(code N)" banner suffix instead of inventing one. Same bug class as the events-feed fix in the preceding commit, on the main chat surface. Co-authored-by: Gille <4317663+helix4u@users.noreply.github.com> * test(dashboard): cover the PTY ticket connect deadline Mirrors the events-feed cases in ChatSidebar.test.tsx: a rejected ticket retries, a stalled ticket times out and its late resolution cannot open a superseded socket, and a settled ticket disarms the deadline so PTY_CONNECTING_TIMEOUT_MS remains the only guard on a wedged handshake (NS-591 regression). Both failure cases fail against ChatPage.tsx without the preceding fix. Co-authored-by: Gille <4317663+helix4u@users.noreply.github.com> --------- Co-authored-by: Gille <4317663+helix4u@users.noreply.github.com>
…supersedes NousResearch#81931) (NousResearch#81978) * fix(dashboard): retry stalled events feed reconnects * fix(dashboard): bound the PTY ticket request before the socket exists ChatPage's connect awaits a single-use ticket from `api.buildWsUrl()` before `new WebSocket()`. That request produces no socket, so a rejection or a hang emits no `close` event and never arms PTY_CONNECTING_TIMEOUT_MS (set after the socket is constructed). The tab stranded on "connecting" with `connectInFlightRef` stuck true, which also suppresses the page-resume reconnect path. Give the ticket phase its own deadline and route both failure modes into the existing backoff. A `ticketSuperseded` flag invalidates a late ticket result so a timed-out attempt cannot open a socket behind the replacement it scheduled, and cleanup clears the timer on unmount. `scheduleReconnect` now takes `number | null` so an attempt that died before any socket existed omits the "(code N)" banner suffix instead of inventing one. Same bug class as the events-feed fix in the preceding commit, on the main chat surface. Co-authored-by: Gille <4317663+helix4u@users.noreply.github.com> * test(dashboard): cover the PTY ticket connect deadline Mirrors the events-feed cases in ChatSidebar.test.tsx: a rejected ticket retries, a stalled ticket times out and its late resolution cannot open a superseded socket, and a settled ticket disarms the deadline so PTY_CONNECTING_TIMEOUT_MS remains the only guard on a wedged handshake (NS-591 regression). Both failure cases fail against ChatPage.tsx without the preceding fix. Co-authored-by: Gille <4317663+helix4u@users.noreply.github.com> --------- Co-authored-by: Gille <4317663+helix4u@users.noreply.github.com>
…supersedes NousResearch#81931) (NousResearch#81978) * fix(dashboard): retry stalled events feed reconnects * fix(dashboard): bound the PTY ticket request before the socket exists ChatPage's connect awaits a single-use ticket from `api.buildWsUrl()` before `new WebSocket()`. That request produces no socket, so a rejection or a hang emits no `close` event and never arms PTY_CONNECTING_TIMEOUT_MS (set after the socket is constructed). The tab stranded on "connecting" with `connectInFlightRef` stuck true, which also suppresses the page-resume reconnect path. Give the ticket phase its own deadline and route both failure modes into the existing backoff. A `ticketSuperseded` flag invalidates a late ticket result so a timed-out attempt cannot open a socket behind the replacement it scheduled, and cleanup clears the timer on unmount. `scheduleReconnect` now takes `number | null` so an attempt that died before any socket existed omits the "(code N)" banner suffix instead of inventing one. Same bug class as the events-feed fix in the preceding commit, on the main chat surface. Co-authored-by: Gille <4317663+helix4u@users.noreply.github.com> * test(dashboard): cover the PTY ticket connect deadline Mirrors the events-feed cases in ChatSidebar.test.tsx: a rejected ticket retries, a stalled ticket times out and its late resolution cannot open a superseded socket, and a settled ticket disarms the deadline so PTY_CONNECTING_TIMEOUT_MS remains the only guard on a wedged handshake (NS-591 regression). Both failure cases fail against ChatPage.tsx without the preceding fix. Co-authored-by: Gille <4317663+helix4u@users.noreply.github.com> --------- Co-authored-by: Gille <4317663+helix4u@users.noreply.github.com>
What does this PR do?
Prevents the Dashboard events feed reconnect loop from stalling permanently after it has already displayed
reconnecting in 1s....The existing reconnect path only scheduled another attempt after a WebSocket
closeevent. In gated Dashboard mode, every attempt first awaitsbuildWsUrl()to mint a fresh single-use ticket. If that request rejected or never settled, no replacement socket existed to emitclose, so the retry loop stopped at its previous banner. A WebSocket handshake that emitted neitheropennorclosehad the same failure mode.This change gives the complete connection attempt, including ticket minting and the opening handshake, a 15-second deadline. Setup failures and deadline expiry feed back into the existing guarded exponential-backoff scheduler. A generation guard prevents late ticket responses or socket events from creating a superseded connection, and cleanup invalidates the active generation and clears both timers on unmount.
Related Issue
Discord support report: https://discord.com/channels/1053877538025386074/1535696700121948190
Follow-up to #79524.
Type of Change
Changes Made
web/src/components/ChatSidebar.tsxweb/src/lib/events-reconnect.tsweb/src/components/ChatSidebar.test.tsxHow to Test
cd web && npx vitest run src/components/ChatSidebar.test.tsx.cd web && npx tsc -p . --noEmit.cd web && npx vitest run.reconnecting in 1s...indefinitely.Checklist
Code
fix(scope):,feat(scope):, etc.)pytest tests/ -qand all tests pass (not run: this is a web-only TypeScript change; the full Python suite is intentionally left to CI)Documentation & Housekeeping
docs/, docstrings) — N/A, no user-facing workflow changedcli-config.yaml.exampleif I added/changed config keys — N/A, no config keys changedCONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — N/A, existing reconnect architecture retainedScreenshots / Logs
Validation from the clean worktree:
ChatSidebar.test.tsx: 16 tests passed.react-refresh/only-export-componentswarning atChatSidebar.tsx:104.git diff --check: clean.