CherryHQ/cherry-studio · error · Error
Slack API error ${method}: HTTP ${response.status} - ${error
Error message
Slack API error ${method}: HTTP ${response.status} - ${errorText} What it means
Thrown by SlackAdapter.apiRequest() when a POST to any Slack Web API method (chat.postMessage, chat.update, auth.test, reactions.add, users.info) returns a non-2xx HTTP status. This is the transport-level branch: Slack did not return a 200 OK. The errorText is the raw response body (best-effort, may be empty). Method names are the Slack Web API method path segment (e.g. 'chat.postMessage').
Source
Thrown at src/main/ai/channels/adapters/slack/SlackAdapter.ts:632
// Best-effort removal
}
}
// ─── Slack Web API Helper ──────────────────────────────────
private async apiRequest(method: string, body: Record<string, unknown>): Promise<unknown> {
const response = await net.fetch(`${SLACK_API_BASE}/${method}`, {
method: 'POST',
headers: {
Authorization: `Bearer ${this.botToken}`,
'Content-Type': 'application/json; charset=utf-8'
},
body: JSON.stringify(body)
})
if (!response.ok) {
const errorText = await response.text().catch(() => '')
throw new Error(`Slack API error ${method}: HTTP ${response.status} - ${errorText}`)
}
const data = (await response.json()) as { ok: boolean; error?: string }
if (!data.ok) {
throw new Error(`Slack API error ${method}: ${data.error ?? 'unknown error'}`)
}
return data
}
// ─── WebSocket Helper ──────────────────────────────────────
private send(payload: object): void {
if (this.ws?.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify(payload))
}
}
View on GitHub (pinned to 726446b54c)
Solutions
- For HTTP 429: implement Retry-After header respect and back off — the existing 100ms inter-chunk delay in sendMessage is too short under heavy load.
- For HTTP 401/403: verify the xoxb- bot token is valid and has the required scopes (chat:write, reactions:write, users:read, files:read).
- For HTTP 5xx: check status.slack.com and let the existing scheduleReconnect()/catch handlers retry.
- Capture response.headers.get('retry-after') before reading the body when status===429.
Example fix
// before — no retry on rate limit, body text may be empty
if (!response.ok) {
const errorText = await response.text().catch(() => '')
throw new Error(`Slack API error ${method}: HTTP ${response.status} - ${errorText}`)
}
// after — honor Retry-After for 429 so callers can back off
if (response.status === 429) {
const retryAfter = Number(response.headers.get('retry-after') ?? '1')
throw new SlackRateLimitError(method, retryAfter)
}
if (!response.ok) {
const errorText = await response.text().catch(() => '')
throw new Error(`Slack API error ${method}: HTTP ${response.status} - ${errorText}`)
} Defensive patterns
Strategy: retry
Validate before calling
// Pre-check the bot token shape to eliminate the most common transport errors (401).
const BOT_TOKEN_RE = /^xoxb-[0-9]+-[0-9]+-[A-Za-z0-9]+$/
if (!BOT_TOKEN_RE.test(config.bot_token)) {
throw new ConfigError('Slack bot token must be xoxb-... format')
}
// Also verify the bot is a member of the target channels before sending:
// (call conversations.list / conversations.info to confirm membership up-front) Type guard
function isSlackHttpError(e: unknown): e is Error {
return e instanceof Error && /Slack API error .*: HTTP \d+/.test(e.message)
}
function extractHttpStatus(e: unknown): number | null {
const m = e instanceof Error ? e.message.match(/HTTP (\d+)/) : null
return m ? Number(m[1]) : null
} Try / catch
// For 429 specifically, honor Retry-After (requires a richer error — see exampleFix).
// With the current plain-Error shape, wrap sends in a bounded retry on 5xx/429:
async function sendWithRetry(adapter, chatId, text, attempts = 3) {
for (let i = 0; i < attempts; i++) {
try {
return await adapter.sendMessage(chatId, text)
} catch (e) {
const status = extractHttpStatus(e)
if (status && (status === 429 || status >= 500) && i < attempts - 1) {
await new Promise((r) => setTimeout(r, 1000 * (i + 1)))
continue
}
throw e
}
}
} Prevention
- Throttle chat.update below 1/s per channel — SLACK_STREAM_THROTTLE_MS=1500 already does this; do not reduce it.
- Invite the bot (@botname) to every channel in allowed_channel_ids before enabling notifications.
- Verify the xoxb- token format at config-write time.
- Check status.slack.com before debugging persistent 5xx — it is often an incident.
When it happens
Trigger: Any apiRequest() call where Slack returns 4xx/5xx: 429 means rate-limited (Retry-After header should be honored), 401 means the bot token (xoxb-) is invalid, 403 means missing scopes, 404 means the method does not exist, 5xx is a Slack incident. The bot token is the credential here, not the app token. This helper is used by sendMessage, onTextUpdate streaming, reactions, users.info, and auth.test.
Common situations: Bursting chat.update calls faster than the ~1/s per-channel Slack limit (the SLACK_STREAM_THROTTLE_MS=1500 constant exists for this) and hitting 429; the bot token was revoked when the app was uninstalled from the workspace; the bot lacks chat:write scope to post in a particular channel; Slack API outage returns 503.
Related errors
- Slack API error ${method}: ${data.error ?? 'unknown error'}
- Failed to open Socket Mode connection: HTTP ${response.statu
- Socket Mode connection failed: ${data.error ?? 'no URL retur
- ${label} returned non-JSON (HTTP ${response.status})
- ${label} failed with HTTP ${response.status}
AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12).
Data as JSON: /api/errors/c7f1f81a640cdcff.
Report an issue: GitHub.