{"record":{"id":"f255d16bd6f58bf8","repo":"CherryHQ/cherry-studio","slug":"failed-to-open-socket-mode-connection-http-resp","errorCode":null,"errorMessage":"Failed to open Socket Mode connection: HTTP ${response.status}","messagePattern":"Failed to open Socket Mode connection: HTTP (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/ai/channels/adapters/slack/SlackAdapter.ts","lineNumber":270,"sourceCode":"      this.log.info('Slack bot identity resolved', { botUserId: this.botUserId })\n    } catch (error) {\n      this.log.warn('Failed to resolve bot user ID', {\n        error: error instanceof Error ? error.message : String(error)\n      })\n    }\n  }\n\n  private async getSocketModeUrl(): Promise<string> {\n    const response = await net.fetch(`${SLACK_API_BASE}/apps.connections.open`, {\n      method: 'POST',\n      headers: {\n        Authorization: `Bearer ${this.appToken}`,\n        'Content-Type': 'application/x-www-form-urlencoded'\n      }\n    })\n\n    if (!response.ok) {\n      throw new Error(`Failed to open Socket Mode connection: HTTP ${response.status}`)\n    }\n\n    const data = (await response.json()) as { ok: boolean; url?: string; error?: string }\n    if (!data.ok || !data.url) {\n      throw new Error(`Socket Mode connection failed: ${data.error ?? 'no URL returned'}`)\n    }\n\n    return data.url\n  }\n\n  private async startSocketMode(): Promise<void> {\n    if (this.shouldStop) return\n\n    try {\n      this.cleanup()\n\n      const wsUrl = await this.getSocketModeUrl()\n      this.log.info('Connecting to Slack Socket Mode')","sourceCodeStart":252,"sourceCodeEnd":288,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/src/main/ai/channels/adapters/slack/SlackAdapter.ts#L252-L288","documentation":"Thrown by SlackAdapter.getSocketModeUrl() after POSTing to Slack's apps.connections.open endpoint when the HTTP response status is not 2xx (response.ok is false). This is a transport-level failure: Slack rejected the request before returning a Socket Mode WSS URL. The app-level token (xapp-) is the credential used here, distinct from the bot token (xoxb-).","triggerScenarios":"A POST to https://slack.com/api/apps.connections.open with the app-level Bearer token returns a non-2xx status. Common causes: the xapp- token is missing/expired/revoked (Slack returns 401), the token has wrong scopes (Socket Mode must be enabled on the app and the token needs connections:write), or Slack itself is returning 5xx during an incident. Unlike the JSON ok:false path, this fires only when the HTTP layer itself is unhealthy.","commonSituations":"The Slack app config was created without enabling Socket Mode, so the app-level token lacks the connections:write scope; the user pasted a bot token (xoxb-) into the app-token field; the token was revoked when the Slack app was reinstalled or the workspace disconnected the app; a corporate proxy/firewall returns a 407 or 502 for slack.com.","solutions":["Verify the app-level token (xapp-) is set and not confused with the bot token (xoxb-) — SlackAdapter.checkReady() requires both (SlackAdapter.ts:222).","In the Slack app config (api.slack.com/apps), confirm Socket Mode is enabled and the app-level token has the connections:write scope.","If the token was regenerated, update the channel config with the new xapp- token and reconnect.","Check status.slack.com for an active incident if the token is valid and the HTTP status is 5xx."],"exampleFix":"// before — wrong token type silently produces 401\nthis.appToken = config.channelConfig.app_token // accidentally holds an xoxb- token\n\n// after — guard at connect time so the error is self-describing\nprotected override async performConnect(_signal: AbortSignal): Promise<void> {\n  if (!this.appToken?.startsWith('xapp-')) {\n    throw new Error('Slack app-level token (xapp-) is required for Socket Mode')\n  }\n  // ...\n}","handlingStrategy":"validation","validationCode":"// Validate the app-level token shape and readiness BEFORE calling performConnect.\n// SlackAdapter.checkReady() (SlackAdapter.ts:222) already returns false for missing tokens;\n// honor it and add a format check.\nconst APP_TOKEN_RE = /^xapp-1-[A-Za-z0-9-]+$/\n\nfunction canOpenSocketMode(appToken: string | undefined): boolean {\n  return typeof appToken === 'string' && APP_TOKEN_RE.test(appToken)\n}\n\n// Usage:\nif (!canOpenSocketMode(channel.config.app_token)) {\n  throw new ConfigError('Slack app-level token (xapp-) is required and must be enabled for Socket Mode')\n}\nawait slackAdapter.connect(signal)","typeGuard":"function isValidSlackAppToken(v: unknown): v is string {\n  return typeof v === 'string' && /^xapp-1-[A-Za-z0-9-]+/.test(v)\n}\n\n// Before connecting:\nif (!isValidSlackAppToken(config.app_token) || !isValidSlackBotToken(config.bot_token)) {\n  return { ok: false, reason: 'invalid-tokens' }\n}","tryCatchPattern":"// getSocketModeUrl is called inside startSocketMode which already wraps in try/catch\n// and calls scheduleReconnect(). Do NOT catch here — let it propagate so the reconnect\n// loop runs. Instead, catch at the adapter-connection boundary and surface a state:\ntry {\n  await adapter.connect(signal)\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('Failed to open Socket Mode connection')) {\n    setChannelState(channelId, 'error', 'Slack Socket Mode handshake failed — check the xapp- token and Socket Mode scope')\n  }\n  throw e\n}","preventionTips":["Enable Socket Mode and grant connections:write on the app-level token before configuring the channel.","Distinguish xapp- (app token) from xoxb- (bot token) at config validation time using a prefix regex.","Honor checkReady() before calling connect() so invalid configs never reach the network call.","Regenerate tokens on api.slack.com whenever scopes change and reinstall the app to the workspace."],"tags":["slack","socket-mode","network","authentication","config"],"backgroundTag":null,"analyzedSha":"726446b54cd69ffe51a276638672f6d95ca0768c","analyzedAt":"2026-08-12T17:30:37.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}