Skip to content

fix(desktop): force-redial half-open gateways after wake - #89092

Closed
yu-xin-c wants to merge 1 commit into
NousResearch:mainfrom
yu-xin-c:codex/89083-force-wake-reconnect
Closed

fix(desktop): force-redial half-open gateways after wake#89092
yu-xin-c wants to merge 1 commit into
NousResearch:mainfrom
yu-xin-c:codex/89083-force-wake-reconnect

Conversation

@yu-xin-c

@yu-xin-c yu-xin-c commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Hermes Desktop already listens for power-resume, browser-online, and manual reconnect signals, but reconnectNow() skips all work while gateway.connectionState is open. macOS can leave that state stale after sleep even when the underlying TCP connection is gone, so prompts are sent into a half-open socket and the window never recovers.

Treat strong recovery signals as transport invalidation:

  • power resume, network online, and the manual Reconnect command close an apparently-open primary socket before running the existing reconnect path
  • the same forced sweep applies to retained secondary gateways
  • ordinary visibilitychange remains non-forcing, so switching windows does not tear down a healthy connection
  • reconnect still flows through the existing backend revalidation, WebSocket URL re-minting, state refresh, and retry/backoff logic

This stays at the Desktop orchestration boundary. Browser WebSocket does not expose native ping frames and the gateway has no JSON-RPC ping contract, so adding a timer here would require a new protocol surface for a case where the OS/browser already provides explicit recovery signals.

This is complementary to #90012: that PR adds capability-gated heartbeat detection when paired with #89958, while this PR keeps power-resume, browser-online, and manual recovery working against older backends that advertise no heartbeat capability.

Related Issue

Fixes #89083

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Security fix
  • Documentation update
  • Tests (adding or improving test coverage)
  • Refactor (no behavior change)
  • New skill (bundled or hub)

Changes Made

  • Update apps/desktop/src/app/gateway/hooks/use-gateway-boot.ts so strong recovery signals can retire an open primary socket and immediately redial it.
  • Extend reconnectSecondaryGateways() with an opt-in forced mode for half-open retained secondaries.
  • Add primary and secondary regressions that leave the socket state open, emit only the recovery action, and verify close plus re-dial.

How to Test

  1. Open Hermes Desktop on macOS and verify chat works.
  2. Sleep and wake the Mac, then send from the same window.
  3. Confirm the existing socket is replaced and chat reconnects without reload.
  4. Run:
    npm run --prefix apps/desktop test:ui
    npm run --prefix apps/desktop typecheck
    npm run --prefix apps/desktop lint
    npx prettier --check on the four changed TypeScript files.

Local results after rebasing onto current main: 539 UI test files / 5,035 tests passed; typecheck passed; ESLint passed with zero errors; formatting passed.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits
  • I searched existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix
  • I've run pytest tests/ -q (N/A: Desktop TypeScript-only change)
  • I've added tests for my changes
  • I've tested on my platform: macOS 26.3.2, Node 22.22.2

Documentation & Housekeeping

  • Documentation update: N/A; no user-facing configuration or command changes
  • cli-config.yaml.example: N/A; no config keys changed
  • CONTRIBUTING.md / AGENTS.md: N/A; no contributor workflow changed
  • Cross-platform impact considered: power resume is Electron-provided on macOS/Windows; browser online is platform-neutral
  • Tool descriptions/schemas: N/A; no tool behavior changed

Screenshots / Logs

No visual UI change. The regression tests exercise the stale-open transport state directly.

@alt-glitch alt-glitch added type/bug Something isn't working comp/desktop Electron desktop app (apps/desktop/*) P2 Medium — degraded but workaround exists labels Aug 18, 2026
@yu-xin-c
yu-xin-c marked this pull request as ready for review August 19, 2026 08:52
@yu-xin-c
yu-xin-c force-pushed the codex/89083-force-wake-reconnect branch from d4eb6d9 to c6c9f66 Compare August 19, 2026 09:02
@Enough1122

Copy link
Copy Markdown
Contributor

AI code review — automated review for reference; please use your judgment.

Correct recovery model, and the signal taxonomy is right: power-resume and network-online are strong signals (the OS may have discarded TCP underneath a still-OPEN WebSocket, so the only honest state is to retire and redial), while visibility stays soft — a sensible gradient rather than blanket force. The redundant-backoff cleanup (clearReconnectTimer() after close() schedules the regular retry) shows the interaction was thought through, secondaries get the same treatment via forceOpenSockets, and both halves are tested against real hook/store code with fake sockets that deliberately keep readyState === OPEN across resume — precisely reproducing the macOS failure mode instead of approximating it. I also verified the blast radius of upgrading registerGatewayReconnect to force semantics: its sole producer is the user-invoked gateway menu "reconnect" action, where destructive redial is exactly what the button promises.

No blocking issues found.

Nit: every wake event now closes a healthy socket too — a 5-minute lid-close on AC power where TCP survived will still eat a disconnect/reconnect cycle and a transient UI state flip on resume. Acceptable as the safe default, but worth a comment recording that tradeoff ("cheap redial beats a half-open socket we can't cheaply probe"), so a future contributor who adds a lightweight ping-probe knows the behavior change they'd be introducing rather than fighting an unexplained close().

— Reviewed by Hermes AI reviewer (reviewer-f2)

@teknium1

Copy link
Copy Markdown
Contributor

Merged via the consolidated session-recovery PR #93361 — your force-redial commit cherry-picked with authorship preserved (power-resume/online/manual reconnect now treat an apparently-open socket as invalid and redial; fixes the #89083 sleep/wake dead app). Rebase conflicts vs the #93292 reconnect block were resolved keeping both behaviors. Thanks @yu-xin-c!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/desktop Electron desktop app (apps/desktop/*) 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.

Desktop: chat window permanently unresponsive after macOS sleep/wake (half-open WebSocket never detected; reconnect guarded by gatewayOpen())

4 participants