{"record":{"id":"96a6673d47f43f19","repo":"Budibase/budibase","slug":"slack-oauth-response-did-not-include-a-bot-token","errorCode":null,"errorMessage":"Slack OAuth response did not include a bot token","messagePattern":"Slack OAuth response did not include a bot token","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/server/src/api/controllers/ai/agents.ts","lineNumber":612,"sourceCode":"\n  await context.doInWorkspaceContext(statePayload.workspaceId, async () => {\n    const agent = await sdk.ai.agents.getOrThrow(statePayload.agentId)\n    const clientId = agent.slackIntegration?.clientId?.trim()\n    const clientSecret = agent.slackIntegration?.clientSecret?.trim()\n    if (!clientId || !clientSecret) {\n      throw new Error(\"Slack OAuth client credentials are not configured\")\n    }\n\n    const redirectUri = await getSlackOAuthRedirectUrl()\n    const token = await sdk.ai.deployments.slack.exchangeSlackOAuthCode({\n      code,\n      clientId,\n      clientSecret,\n      redirectUri,\n    })\n    const botToken = token.access_token?.trim()\n    if (!botToken) {\n      throw new Error(\"Slack OAuth response did not include a bot token\")\n    }\n\n    const updatedAgent = await sdk.ai.agents.update({\n      ...agent,\n      slackIntegration: {\n        ...agent.slackIntegration,\n        appId: token.app_id?.trim() || agent.slackIntegration?.appId,\n        botToken,\n        botUserId: token.bot_user_id?.trim() || undefined,\n        teamId: token.team?.id?.trim() || undefined,\n        teamName: token.team?.name?.trim() || undefined,\n      },\n    })\n    await publishSlackIntegrationForLiveAgent(updatedAgent)\n  })\n\n  ctx.redirect(\n    `/builder/workspace/${statePayload.workspaceId}/agent/${statePayload.agentId}/deployment?slack_connected=1`","sourceCodeStart":594,"sourceCodeEnd":630,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/server/src/api/controllers/ai/agents.ts#L594-L630","documentation":"After exchanging the authorization code via sdk.ai.deployments.slack.exchangeSlackOAuthCode, the handler expects token.access_token (the Slack bot token, xoxb-...). If the exchange response has no access_token, it throws this Error since the agent cannot operate without a bot token.","triggerScenarios":"Slack's oauth.v2.access response lacks access_token — the code was invalid/already used (Slack returns ok:false with an error field instead), wrong clientId/clientSecret, redirect_uri mismatch with the one used at authorize time, or missing scope grant for a bot token.","commonSituations":"Replayed/expired authorization code; redirect_uri passed to the exchange differing from the authorize request; Slack app missing bot scopes/incorrect app type; Slack API incident returning malformed responses.","solutions":["Log the full exchange response — it usually contains an 'error' field (invalid_code, bad_redirect_uri, etc.) explaining the failure","Restart the OAuth flow so a fresh, unused code is exchanged","Ensure the redirectUri passed to exchangeSlackOAuthCode exactly matches the one used in the authorize URL and Slack app settings","Verify clientId/clientSecret are correct and the Slack app has bot token scopes configured"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"const token = await sdk.ai.deployments.slack.exchangeSlackOAuthCode({ code, clientId, clientSecret, redirectUri })\nif (!token.access_token?.trim()) {\n  // inspect token.error (e.g. invalid_code, bad_redirect_uri) before updating the agent\n  console.error(\"Slack token exchange failed:\", token)\n}","typeGuard":"function hasBotToken(t: { access_token?: string }): t is { access_token: string } {\n  return typeof t.access_token === \"string\" && t.access_token.trim() !== \"\"\n}","tryCatchPattern":"try {\n  await completeSlackOAuth(ctx)\n} catch (err) {\n  if (err.message.includes(\"did not include a bot token\")) {\n    // log exchange response error field, restart OAuth with a fresh code and matching redirect_uri\n  } else { throw err }\n}","preventionTips":["Ensure redirectUri in the exchange matches the authorize request and Slack app settings exactly","Only exchange fresh codes — never reuse a code from a previous callback","Verify Slack app bot scopes are configured so v2 access returns a bot token","Capture and log the full exchange response for diagnosis (redacting secrets)"],"tags":["slack","oauth","token-exchange","bot-token"],"backgroundTag":"oauth-token-exchange-failed","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}