NousResearch/hermes-agent · error · Error

Local Hermes backend is HTTP-reachable but the WebSocket (/a

Error message

Local Hermes backend is HTTP-reachable but the WebSocket (/api/ws) rejected the session token: ${wsProbe.reason}

What it means

Local-boot path variant of the WS token verification: the locally started Hermes backend (dashboard/serve child) answered HTTP, adoptServedDashboardToken obtained the session token from the child, but the subsequent WebSocket probe to ws://127.0.0.1:<port>/api/ws?token=... was rejected. Boot aborts at ~progress-94 stage because the real app transport (WS) cannot authenticate even though REST works. It almost always indicates token mismatch between the spawned child and the probe, or an unexpected process on the port.

Source

Thrown at apps/desktop/electron/main.ts:8614

    }

    const baseUrl = `http://127.0.0.1:${port}`
    await advanceBootProgress('backend.wait', 'Waiting for Hermes backend to become ready', 90)
    await Promise.race([waitForHermes(baseUrl, token), backendStartFailed])
    backendReady = true
    backendStartFailure = null

    const authToken = await adoptServedDashboardToken(baseUrl, token, {
      childAlive: () => hermesProcess.exitCode === null && !hermesProcess.killed,
      rememberLog
    })

    // Verify the WebSocket session token before declaring backend ready.
    const wsUrl = `ws://127.0.0.1:${port}/api/ws?token=${encodeURIComponent(authToken)}`
    const wsProbe = await probeGatewayWebSocket(wsUrl, { WebSocketImpl: globalThis.WebSocket })

    if (!wsProbe.ok) {
      throw new Error(
        `Local Hermes backend is HTTP-reachable but the WebSocket (/api/ws) rejected the session token: ${wsProbe.reason}`
      )
    }

    updateBootProgress({
      phase: 'backend.ready',
      message: 'Hermes backend is ready. Finalizing desktop startup',
      progress: 94,
      running: true,
      error: null
    })

    // A successful boot (including a soft restart that the repair-guard
    // chose over a hard reinstall, see #74874) means any in-flight repair
    // attempt counter has been honoured — reset it so the next genuine
    // failure starts fresh from attempt 1 instead of inheriting the
    // accumulated count of the resolved episode.
    bootstrapRepairAttempt = 0

View on GitHub (pinned to c896c09c42)

Solutions

  1. Inspect wsProbe.reason in the thrown message for 401/403 vs connection-closed
  2. Kill orphan hermes serve/dashboard processes (pgrep -af 'hermes (serve|dashboard)') and retry boot
  3. Reinstall/align the managed hermes runtime with the desktop app version
  4. Check the child's captured logs (rememberLog output surfaced in boot diagnostics) for auth errors on /api/ws
  5. Retry once after a clean disconnect; transient adoption-vs-readiness races resolve on a fresh boot
Defensive patterns

Strategy: retry

Type guard

function isLocalWsAuthFailure(e: unknown): boolean {
  return e instanceof Error && e.message.startsWith('Local Hermes backend is HTTP-reachable but the WebSocket')
}

Try / catch

try { await startLocalBackend() } catch (e) { if (isLocalWsAuthFailure(e)) { await cleanupOrphanBackends(); await startLocalBackend() } else throw e }

Prevention

When it happens

Trigger: probeGatewayWebSocket on the adopted dashboard token fails: token rejected during WS upgrade (401/403), the adopted token belongs to a different (stale) process on the same port, or the child died between the HTTP adoption and the WS probe.

Common situations: Leftover 'hermes dashboard'/'hermes serve' process from a crashed prior session occupying the port; mixed versions after an app update (runtime adopts one token scheme, desktop probes another); very fast machines/timing windows where the child rotates tokens at readiness.

Related errors


AI-assisted analysis of NousResearch/hermes-agent@c896c09c42 (2026-08-14). Data as JSON: /api/errors/2193b1467e7e79cb. Report an issue: GitHub.