{"record":{"id":"5f85b35df9f3c6e2","repo":"CherryHQ/cherry-studio","slug":"failed-to-get-gateway-url-http-response-status","errorCode":null,"errorMessage":"Failed to get gateway URL: HTTP ${response.status} - ${errorText}","messagePattern":"Failed to get gateway URL: HTTP (.+?) - (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/ai/channels/adapters/discord/DiscordAdapter.ts","lineNumber":280,"sourceCode":"      controller.dispose()\n    }\n    this.streamingControllers.clear()\n    this.cleanup()\n    this.log.info('Discord bot stopped')\n  }\n\n  // ─── Gateway Connection ───────────────────────────────────────\n\n  private async getGatewayUrl(): Promise<string> {\n    const response = await net.fetch(`${DISCORD_API_BASE}/gateway/bot`, {\n      headers: {\n        Authorization: `Bot ${this.botToken}`,\n        'User-Agent': USER_AGENT\n      }\n    })\n    if (!response.ok) {\n      const errorText = await response.text().catch(() => '')\n      throw new Error(`Failed to get gateway URL: HTTP ${response.status} - ${errorText}`)\n    }\n    const data = (await response.json()) as { url: string }\n    return data.url\n  }\n\n  private async startGateway(): Promise<void> {\n    if (this.isConnecting || this.shouldStop) return\n    this.isConnecting = true\n\n    try {\n      this.cleanup()\n\n      const gatewayUrl = this.resumeGatewayUrl ?? (await this.getGatewayUrl())\n      const wsUrl = `${gatewayUrl}?v=10&encoding=json`\n      this.log.info('Connecting to Discord gateway', { url: wsUrl })\n\n      const ws = new WebSocket(wsUrl)\n      this.ws = ws","sourceCodeStart":262,"sourceCodeEnd":298,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/src/main/ai/channels/adapters/discord/DiscordAdapter.ts#L262-L298","documentation":"Thrown by DiscordAdapter.getGatewayUrl when the request to GET /gateway/bot returns a non-2xx status. The message includes the HTTP status and Discord's error body, so the exact cause is recoverable: 401 means the token is invalid, 429 means rate-limited, 5xx means Discord-side trouble. This call is the first step of startGateway, so a failure here aborts the gateway connection.","triggerScenarios":"performConnect -> startGateway -> getGatewayUrl issues an authenticated request to DISCORD_API_BASE/gateway/bot and response.ok is false.","commonSituations":"Invalid/revoked bot token (401); missing the bot scope; rate-limited by Discord (429); Discord API outage (5xx); network/proxy returning an error status; clock skew or TLS interception breaking the request.","solutions":["Read the status in the message: 401 → regenerate/fix the bot token; 429 → back off and retry with rate-limit handling; 5xx → retry after a short delay.","Confirm the token has the bot scope and is not revoked in the Discord Developer Portal.","Retry connect with exponential backoff for transient (429/5xx) failures.","Check network/proxy/TLS interception that could be returning a non-Discord error body."],"exampleFix":"// before: single-shot connect blows up on transient failures\nawait discordAdapter.connect()\n\n// after: token check + backoff for transient gateway errors\nif (!botToken) throw new Error('Discord bot token is required')\nfor (let attempt = 0; attempt < 3; attempt++) {\n  try { await discordAdapter.connect(); break }\n  catch (e) {\n    if (!/HTTP (429|5\\d\\d)/.test(String(e)) || attempt === 2) throw e\n    await sleep(2 ** attempt * 1000)\n  }\n}","handlingStrategy":"retry","validationCode":"async function gatewayReachable(token: string): Promise<boolean> {\n  const res = await net.fetch(`${DISCORD_API_BASE}/gateway/bot`, {\n    headers: { Authorization: `Bot ${token}`, 'User-Agent': USER_AGENT }\n  })\n  return res.ok\n}\nif (!(await gatewayReachable(botToken))) {\n  // surface a specific cause (token/rate-limit/Discord outage) before connect\n}","typeGuard":"function isTransientGatewayError(e: unknown): boolean {\n  return e instanceof Error && /HTTP (401|429|5\\d\\d)/.test(e.message)\n}","tryCatchPattern":"for (let attempt = 0; attempt < 3; attempt++) {\n  try {\n    await discordAdapter.connect(); break\n  } catch (e) {\n    const transient = e instanceof Error && /HTTP (429|5\\d\\d)/.test(e.message)\n    if (!transient || attempt === 2) throw e\n    await new Promise((r) => setTimeout(r, 2 ** attempt * 1000))\n  }\n}","preventionTips":["Validate the bot token (and bot scope) before connecting.","Retry with exponential backoff for 429/5xx responses.","Surface the HTTP status from the message to pick the right remedy."],"tags":["channels","discord","network","auth","rate-limit","retry"],"backgroundTag":null,"analyzedSha":"726446b54cd69ffe51a276638672f6d95ca0768c","analyzedAt":"2026-08-12T17:30:37.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}