{"record":{"id":"6ac3a466bff59ba2","repo":"Budibase/budibase","slug":"failed-to-rotate-slack-app-configuration-token","errorCode":null,"errorMessage":"Failed to rotate Slack app configuration token","messagePattern":"Failed to rotate Slack app configuration token","errorType":"http","errorClass":"HTTPError","httpStatus":null,"severity":"error","filePath":"packages/server/src/sdk/workspace/ai/deployments/slack.ts","lineNumber":263,"sourceCode":"}\n\nexport const rotateSlackConfigToken = async ({\n  refreshToken,\n}: {\n  refreshToken: string\n}) => {\n  const response = await fetch(`${SLACK_API_BASE}/tooling.tokens.rotate`, {\n    method: \"POST\",\n    headers: {\n      \"Content-Type\": \"application/x-www-form-urlencoded\",\n    },\n    body: new URLSearchParams({\n      refresh_token: refreshToken,\n    }),\n  })\n  const payload = (await response.json()) as SlackConfigTokenRotateResponse\n  if (!response.ok) {\n    throw new HTTPError(\n      \"Failed to rotate Slack app configuration token\",\n      response.status\n    )\n  }\n\n  const rotated = assertSlackOk(\n    payload,\n    \"Failed to rotate Slack app configuration token\"\n  )\n  if (!rotated.token || !rotated.refresh_token || !rotated.exp) {\n    throw new HTTPError(\n      \"Slack app configuration token rotation response was incomplete\",\n      400\n    )\n  }\n\n  return {\n    configToken: rotated.token,","sourceCodeStart":245,"sourceCodeEnd":281,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/server/src/sdk/workspace/ai/deployments/slack.ts#L245-L281","documentation":"This error is thrown by rotateSlackConfigToken when the HTTP response from Slack's app configuration token rotation endpoint is non-ok. Token rotation exchanges a refresh token for a new config token + refresh token pair; an HTTP-level failure means the rotation request was rejected by Slack before producing a structured API payload.","triggerScenarios":"POSTing refresh_token to Slack's config token rotate endpoint and receiving 4xx/5xx - e.g. 400/401 from a malformed or empty refresh_token field, wrong endpoint/auth, or 5xx from Slack.","commonSituations":"The stored refresh token was emptied by a partial save or env var absence (empty string posted); Slack app deleted so the token's parent no longer exists; transient Slack outage; calling rotation with an access token instead of a refresh token.","solutions":["Confirm a non-empty refresh token is being passed - inspect the stored value before calling rotateSlackConfigToken.","Pass the refresh token (not the config/access token) to the rotation endpoint.","If the Slack app was deleted, redo the Slack app setup to get a new config/refresh token pair.","For 5xx statuses, retry the rotation after a short delay."],"exampleFix":"// before\nawait rotateSlackConfigToken({ refreshToken: undefined }) // HTTP 400\n// after\nif (!stored.refreshToken) throw new Error(\"Refresh Slack config token via setup first\")\nconst rotated = await rotateSlackConfigToken({ refreshToken: stored.refreshToken })\nawait persist({ configToken: rotated.configToken, refreshToken: rotated.refreshToken })","handlingStrategy":"try-catch","validationCode":"if (!refreshToken?.trim()) {\n  throw new Error(\"Cannot rotate Slack config token: refresh token missing; redo Slack setup\")\n}","typeGuard":null,"tryCatchPattern":"try {\n  const rotated = await rotateSlackConfigToken({ refreshToken })\n  await persist(rotated) // save both new configToken and refreshToken\n  return rotated\n} catch (e) {\n  if (e instanceof HTTPError && e.status >= 500) {\n    await sleep(5000) // transient Slack issue, safe to retry\n    return rotateSlackConfigToken({ refreshToken })\n  }\n  if (e instanceof HTTPError && e.status === 400) {\n    throw new Error(\"Refresh token rejected; repeat the Slack app setup\")\n  }\n  throw e\n}","preventionTips":["Persist rotation results atomically (config token + refresh token together).","Check refresh token presence before calling rotation; missing token means redo setup, not retry.","Only retry rotation on 5xx; 4xx means the token itself is bad.","Rotate well before the config token's expiry window closes."],"tags":["slack","token-rotation","oauth","http"],"backgroundTag":"token-refresh-failed","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}