Budibase/budibase · error · Error
Slack OAuth callback is missing state
Error message
Slack OAuth callback is missing state
What it means
completeSlackOAuth reads the OAuth state parameter from the callback query. Slack redirects back with ?state=...; if it is absent or empty the handler throws a plain Error because the callback cannot be matched to a pending OAuth initiation stored in cache.
Source
Thrown at packages/server/src/api/controllers/ai/agents.ts:573
})
ctx.body = {
success: true,
appId,
messagingEndpointUrl: slackAppDeployment.messagingEndpointUrl,
oauthAuthorizeUrl: buildSlackInstallUrl({
oauthAuthorizeUrl,
redirectUri: oauthRedirectUrl,
state,
}),
}
ctx.status = 200
}
export async function completeSlackOAuth(ctx: UserCtx<void, void>) {
const state = String(ctx.query.state || "").trim()
if (!state) {
throw new Error("Slack OAuth callback is missing state")
}
const cacheKey = getSlackOAuthStateCacheKey(state)
const statePayload = (await cache.get(cacheKey, {
useTenancy: false,
})) as SlackOAuthState | undefined
await cache.destroy(cacheKey, { useTenancy: false })
if (!statePayload?.agentId || !statePayload.workspaceId) {
throw new Error("Slack OAuth state is invalid or expired")
}
const oauthError = String(ctx.query.error || "").trim()
if (oauthError) {
throw new Error("Slack OAuth authorization failed")
}
const code = String(ctx.query.code || "").trim()
if (!code) {View on GitHub (pinned to a81a902e9a)
Solutions
- Restart the OAuth flow from the beginning (re-initiate so a fresh state is generated and embedded in the Slack authorize URL)
- Check that the redirect/authorize URL construction includes state and that the registered Slack redirect URL preserves query params
- Have the user use the original install link rather than a saved callback URL
- Confirm no reverse proxy or middleware is stripping query parameters
Example fix
// before
const state = String(ctx.query.state || "").trim()
// after (caller-side check before hitting the callback)
if (!url.searchParams.get("state")) {
url.searchParams.set("state", generatedState)
} Defensive patterns
Strategy: validation
Validate before calling
const state = new URL(callbackUrl).searchParams.get("state")
if (!state) {
// restart the flow instead of calling the callback handler
return initiateSlackOAuth(agentId)
} Type guard
function hasOAuthState(q: Record<string, unknown>): q is Record<string, string> & { state: string } {
return typeof q.state === "string" && q.state.trim() !== ""
} Try / catch
try {
await completeSlackOAuth(ctx)
} catch (err) {
if (err.message.includes("missing state")) {
// redirect the user to restart the Slack installation flow
} else { throw err }
} Prevention
- Always build the Slack authorize URL with a state parameter
- Never share or bookmark the callback URL; it is flow-internal
- Ensure proxies preserve query strings on redirects
- Surface a friendly 'restart install' page when state is missing
When it happens
Trigger: User lands on the callback URL without a state query param — bookmarked/stale callback URL, manually edited URL, or the initiate step never encoded state into the authorize URL.
Common situations: Browser truncating/mangling the query string; proxies stripping query params; users re-opening an old callback link from history; broken authorize URL construction.
Related errors
- Slack OAuth callback is missing the authorization code
- Slack app creation response was incomplete
- Slack OAuth state is invalid or expired
- Slack OAuth authorization failed
- Slack OAuth client credentials are not configured
AI-assisted analysis of Budibase/budibase@a81a902e9a (2026-08-29).
Data as JSON: /api/errors/e9924de9615506b6.
Report an issue: GitHub.