{"record":{"id":"86437db3ea646309","repo":"NousResearch/hermes-agent","slug":"hermes-backend-for-profile-profile-is-http-re","errorCode":null,"errorMessage":"Hermes backend for profile \"${profile}\" is HTTP-reachable but the WebSocket (/api/ws) rejected the session token: ${wsProbe.reason}","messagePattern":"Hermes backend for profile \"(.+?)\" is HTTP-reachable but the WebSocket \\(/api/ws\\) rejected the session token: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/desktop/electron/main.ts","lineNumber":8274,"sourceCode":"  const baseUrl = `http://127.0.0.1:${port}`\n  await Promise.race([waitForHermes(baseUrl, token), startFailed])\n  ready = true\n\n  const authToken = await adoptServedDashboardToken(baseUrl, token, {\n    childAlive: () => child.exitCode === null && !child.killed,\n    label: `Hermes backend for profile \"${profile}\"`,\n    rememberLog\n  })\n\n  entry.token = authToken\n\n  // Verify the WebSocket session token before declaring backend ready.\n  // HTTP /api/status can pass while WS auth fails (separate transport, separate guards).\n  const wsUrl = `ws://127.0.0.1:${port}/api/ws?token=${encodeURIComponent(authToken)}`\n  const wsProbe = await probeGatewayWebSocket(wsUrl, { WebSocketImpl: globalThis.WebSocket })\n\n  if (!wsProbe.ok) {\n    throw new Error(\n      `Hermes backend for profile \"${profile}\" is HTTP-reachable but the WebSocket (/api/ws) rejected the session token: ${wsProbe.reason}`\n    )\n  }\n\n  return {\n    baseUrl,\n    mode: 'local',\n    source: 'local',\n    authMode: 'token',\n    token: authToken,\n    profile,\n    wsUrl,\n    logs: hermesLog.slice(-80),\n    ...getWindowState()\n  }\n}\n\nfunction stopPoolBackend(profile) {","sourceCodeStart":8256,"sourceCodeEnd":8292,"githubUrl":"https://github.com/NousResearch/hermes-agent/blob/c896c09c42910c584c4c7d2325b58c14713ea42c/apps/desktop/electron/main.ts#L8256-L8292","documentation":"During desktop boot for a given profile, the locally spawned Hermes backend answered HTTP on 127.0.0.1:<port>, but the verification WebSocket to /api/ws?token=... rejected the session token that was just adopted. Because HTTP /api/status and WS auth are separate transports with separate guards, the HTTP leg can pass while WS auth fails. The message exists to surface that split explicitly instead of letting boot fail later with a generic connect error.","triggerScenarios":"Profile backend spawn path: HTTP status check passes, adoptServedDashboardToken/authToken is obtained, then probeGatewayWebSocket(wsUrl) returns !ok — e.g. the gateway rejected the token (401/403 on upgrade), the token was minted for a different session/expired between HTTP check and WS probe, or a stale process is squatting on the port.","commonSituations":"A previous Hermes backend instance still holds the port so the new spawn's token is checked against the old process; gateway version mismatch where the token format or WS auth changed; slow machines where the token TTL lapses during boot; gateway auth middleware rejecting query-param tokens in favor of tickets.","solutions":["Read wsProbe.reason in the message to see if it is an HTTP 401/403 during upgrade (auth) vs timeout/closed (process)","Kill stale Hermes backend processes for that profile and retry the boot so a fresh process owns the port","Update the desktop app and the hermes runtime to matching versions so token minting and WS auth agree","Check gateway logs (~/.hermes/logs/gateway.log for the profile) for WS handshake rejections at boot time","Retry the connection once — a token adopted during process warmup can fail transiently on first probe"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":"function isProfileWsAuthFailure(e: unknown, profile: string): boolean {\n  return e instanceof Error && e.message.includes(`profile \"${profile}\" is HTTP-reachable but the WebSocket`)\n}","tryCatchPattern":"try { await bootProfileBackend(profile) } catch (e) { if (isProfileWsAuthFailure(e, profile)) { await killStaleHermesBackends(profile); await bootProfileBackend(profile) } else throw e }","preventionTips":["Ensure clean shutdown of the desktop app so backend children don't linger on their ports","Keep desktop app and hermes runtime versions aligned so token schemes match","Treat boot-time WS auth failures as retryable once, hard-fail only on repeat"],"tags":["websocket","auth","session-token","gateway","desktop","startup"],"backgroundTag":null,"analyzedSha":"c896c09c42910c584c4c7d2325b58c14713ea42c","analyzedAt":"2026-08-14T17:18:01.089Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}