{"record":{"id":"2bd4c58d4d703541","repo":"NousResearch/hermes-agent","slug":"gateway-did-not-return-a-ws-ticket","errorCode":null,"errorMessage":"Gateway did not return a WS ticket.","messagePattern":"Gateway did not return a WS ticket\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/desktop/electron/main.ts","lineNumber":6341,"sourceCode":"// Prefers a native bearer token (cookieless RFC 8252 flow) when present,\n// falling back to the OAuth cookie partition otherwise.\n// Throws (with statusCode 401) if the session cookie is missing/expired —\n// callers treat that as \"needs re-login\".\nasync function mintGatewayWsTicket(baseUrl) {\n  // Native flow: mint the ticket with the bearer token, no cookie involved.\n  const nativeAt = await ensureNativeAccessToken(baseUrl).catch(() => null)\n\n  if (nativeAt) {\n    const body = (await fetchJson(`${baseUrl}/api/auth/ws-ticket`, null, {\n      method: 'POST',\n      timeoutMs: 8_000,\n      bearer: nativeAt\n    })) as any\n\n    const ticket = body?.ticket\n\n    if (!ticket || typeof ticket !== 'string') {\n      throw new Error('Gateway did not return a WS ticket.')\n    }\n\n    return ticket\n  }\n\n  const body = (await fetchJsonViaOauthSession(`${baseUrl}/api/auth/ws-ticket`, {\n    method: 'POST',\n    timeoutMs: 8_000\n  })) as any\n\n  const ticket = body?.ticket\n\n  if (!ticket || typeof ticket !== 'string') {\n    throw new Error('Gateway did not return a WS ticket.')\n  }\n\n  return ticket\n}","sourceCodeStart":6323,"sourceCodeEnd":6359,"githubUrl":"https://github.com/NousResearch/hermes-agent/blob/c896c09c42910c584c4c7d2325b58c14713ea42c/apps/desktop/electron/main.ts#L6323-L6359","documentation":"First of two identical guards in the WS-ticket minting helper: when a native access token was obtained (ensureNativeAccessToken succeeded) and POST {baseUrl}/api/auth/ws-ticket returned a body whose `ticket` is missing or not a string. The request itself succeeded (fetchJson resolved) — the response shape is wrong, typically an error payload or auth rejection that still returned 200/JSON.","triggerScenarios":"Gateway version that predates the /api/auth/ws-ticket endpoint returning some other JSON; token valid enough to avoid a fetch error but rejected by the endpoint, returning {error: ...}; a proxy in front of the gateway rewriting the response.","commonSituations":"Desktop app newer than the gateway backend (endpoint missing); expired-but-parseable auth responses; reverse proxies injecting a JSON body.","solutions":["Update the Hermes backend so /api/auth/ws-ticket exists and returns {ticket: string}.","Re-authenticate: the native access token may be stale — force a re-login and retry ticket minting.","Inspect the actual response body (log it) to see what the gateway returned instead of a ticket.","Bypass proxies that may rewrite the response when testing locally."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"function isWsTicketResponse(body) {\n  return body != null && typeof body.ticket === 'string' && body.ticket.length > 0\n}","tryCatchPattern":"try {\n  const ticket = await mintWsTicket(baseUrl)\n} catch (e) {\n  if (/did not return a WS ticket/.test(e.message)) {\n    await reAuthenticateAndRetry() // stale native token or old gateway\n  } else throw e\n}","preventionTips":["Keep the gateway backend updated alongside the app","Refresh the native access token on auth-shaped failures","Log response bodies when the ticket shape mismatches"],"tags":["gateway","websocket","auth","ticket","network","desktop"],"backgroundTag":null,"analyzedSha":"c896c09c42910c584c4c7d2325b58c14713ea42c","analyzedAt":"2026-08-14T17:18:01.089Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}