Skip to content

fix(dashboard): bound WS ticket minting on the events + PTY sockets (supersedes #81931) - #81978

Merged
austinpickett merged 3 commits into
mainfrom
fix/dashboard-ws-ticket-connect-deadline
Aug 8, 2026
Merged

fix(dashboard): bound WS ticket minting on the events + PTY sockets (supersedes #81931)#81978
austinpickett merged 3 commits into
mainfrom
fix/dashboard-ws-ticket-connect-deadline

Conversation

@austinpickett

Copy link
Copy Markdown
Collaborator

Summary

Supersedes #81931.

The dashboard opens three WebSockets, and each one first awaits a fresh single-use ticket from api.buildWsUrl() before calling new WebSocket(). That await is the whole problem: it produces no socket, so a ticket request that rejects or never settles emits no close event and there is nothing for the retry logic to react to. The connection state machine stops where it stood.

@helix4u caught and fixed this on the events feed in #81931 — the sidebar would strand on reconnecting in 1s... forever. That diagnosis is correct and the fix is kept here essentially verbatim, as the first commit.

The same structure exists on ChatPage's PTY socket, which is the main chat surface rather than a sidebar title feed. PTY_CONNECTING_TIMEOUT_MS (NS-591) does not cover it: that timer is armed after new WebSocket(url) returns, so a ticket request that never settles never arms it. The tab sits on "connecting" with connectInFlightRef stuck true, which additionally suppresses the page-resume reconnect path — so returning to the tab can't recover it either. ChatPage had already solved the second half of this failure mode (a wedged handshake) and left the first half (the await gap) open; this PR closes it with the same shape, so the two halves match.

HermesConsoleModal has the third instance of the pattern but already catches, and has no retry loop to strand — left alone deliberately.

What this PR does

  • Events feed (@helix4u, unchanged): bounds ticket minting plus the opening handshake with EVENTS_CONNECT_TIMEOUT_MS, feeds setup failures and deadline expiry into the existing guarded backoff, and adds a generation guard so a late ticket can't create a superseded socket.
  • PTY chat (new): gives the ticket phase its own PTY_TICKET_TIMEOUT_MS deadline, routes rejection and expiry into the existing scheduleReconnect backoff, and uses a ticketSuperseded flag so a timed-out attempt's late ticket cannot open a socket behind its replacement. Cleanup clears the timer and invalidates the attempt 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.

The new constant sits next to PTY_CONNECTING_TIMEOUT_MS in web/src/lib/pty-reconnect.ts, the module that already owns this policy — no new module, no new config key.

What was dropped from #81931

Nothing. The original three commits' worth of behavior is the first commit here, unmodified.

Test plan

  • cd web && npx vitest run → 27 files, 197 tests pass (194 on main + 3 new PTY cases)
  • cd web && npm run check (typecheck + test + lint, the CI gate) → 0 errors
  • Bug-class verification: reverting only ChatSidebar.tsx fails exactly the 3 events-feed tests; reverting only ChatPage.tsx fails exactly the 2 new PTY failure-path tests. Neither set is a false green.
  • The third PTY test is an NS-591 regression guard: once the socket exists the ticket deadline is disarmed, so PTY_CONNECTING_TIMEOUT_MS stays the only thing that may force-close a wedged handshake.

Reported on Discord: https://discord.com/channels/1053877538025386074/1535696700121948190
Follow-up to #79524.

Credit: @helix4u (primary — original diagnosis and the events-feed fix, carried via Co-authored-by).

helix4u and others added 3 commits August 8, 2026 15:28
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>
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>
@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

૮ >ﻌ< ა ci review

ran on 5acd662

⚠️ Warnings

OSV vulnerability scan · View job

64 known vulnerabilities found in pinned dependencies.

How to fix:

Review the findings in the Security tab. Update the affected dependencies if a patched version is available.


debug info

CI timings

CI timings · View report · View job

Wall time 6m41s vs 6m38s (+0.8%). 10 job(s) slower, 5 faster, 2 unchanged.

  • JS & TS checks / apps/desktop / check:test:ui: +17.0s
  • JS & TS checks / apps/shared / check: +16.0s
  • JS & TS checks / apps/desktop / check:lint: +10.0s
  • JS & TS checks / ui-tui/packages/hermes-ink / check: -9.0s
  • JS & TS checks / tests-js / check: +9.0s

@alt-glitch alt-glitch added type/bug Something isn't working comp/dashboard Web dashboard / control panel UI (dashboard/, landing) P2 Medium — degraded but workaround exists labels Aug 8, 2026
@austinpickett
austinpickett requested a lite review from Copilot August 8, 2026 19:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a reconnection dead-end in the dashboard’s WebSocket flows by ensuring the pre-socket ticket-minting phase is also bounded by timeouts, so failures that occur before new WebSocket() exists still route into the existing backoff/retry state machines (events feed + PTY chat).

Changes:

  • Add an events connect deadline (EVENTS_CONNECT_TIMEOUT_MS) that covers ticket minting + handshake, with generation-guarding to prevent late tickets from creating superseded sockets.
  • Add a PTY ticket deadline (PTY_TICKET_TIMEOUT_MS) that covers the pre-socket ticket request, wiring timeout/rejection into the existing PTY reconnect backoff and preventing late tickets from opening stale sockets.
  • Extend PTY reconnect scheduling to accept number | null so attempts that fail before any socket exists don’t fabricate close codes in the banner.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
File Description
web/src/pages/ChatPage.tsx Adds a bounded ticket-mint phase and supersession guard for the PTY WebSocket connection attempt, feeding failures into the existing reconnect/backoff path.
web/src/pages/ChatPage.test.tsx Adds PTY-specific tests for ticket rejection, ticket hang + late resolution, and the “ticket deadline disarmed once socket exists” regression guard.
web/src/lib/pty-reconnect.ts Introduces PTY_TICKET_TIMEOUT_MS alongside existing PTY reconnect policy constants.
web/src/lib/events-reconnect.ts Introduces EVENTS_CONNECT_TIMEOUT_MS for bounding events feed ticket minting + handshake.
web/src/components/ChatSidebar.tsx Wraps the entire events socket connection attempt with a timeout and generation guard so pre-socket failures can’t strand reconnect state.
web/src/components/ChatSidebar.test.tsx Adds/updates tests covering URL construction rejection, stalled URL request, and stalled handshake under the new connection deadline.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@austinpickett
austinpickett merged commit 2382f50 into main Aug 8, 2026
38 checks passed
@austinpickett
austinpickett deleted the fix/dashboard-ws-ticket-connect-deadline branch August 8, 2026 20:01
ma1138569845 pushed a commit to ma1138569845/dechnicAuditor-agent that referenced this pull request Aug 10, 2026
…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>
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
…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>
blut-agent pushed a commit to blut-agent/hermes-agent-fork that referenced this pull request Aug 11, 2026
…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>
33hodl pushed a commit to 33hodl/hermes-agent that referenced this pull request Aug 12, 2026
…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>
prmartinow pushed a commit to prmartinow/hermes-agent that referenced this pull request Aug 26, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/dashboard Web dashboard / control panel UI (dashboard/, landing) P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants