{"record":{"id":"4f0134e33c18969b","repo":"mastra-ai/mastra","slug":"token-rotation-failed-data-error","errorCode":null,"errorMessage":"Token rotation failed: ${data.error}","messagePattern":"Token rotation failed: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"channels/slack/src/client.ts","lineNumber":90,"sourceCode":"      }),\n      signal: AbortSignal.timeout(SLACK_API_TIMEOUT_MS),\n    });\n\n    const data = (await response.json()) as {\n      ok: boolean;\n      error?: string;\n      token?: string;\n      refresh_token?: string;\n    };\n\n    if (!data.ok) {\n      if (data.error === 'invalid_refresh_token') {\n        throw new Error(\n          'Slack refresh token is invalid. Get fresh tokens from https://api.slack.com/apps > \"Your App Configuration Tokens\". ' +\n            'This can happen if storage was lost or the token was already used.',\n        );\n      }\n      throw new Error(`Token rotation failed: ${data.error}`);\n    }\n\n    if (!data.token || !data.refresh_token) {\n      throw new Error('Token rotation returned incomplete data');\n    }\n\n    this.#token = data.token;\n    this.#refreshToken = data.refresh_token;\n\n    if (this.#onTokenRotation) {\n      await this.#onTokenRotation({\n        token: this.#token,\n        refreshToken: this.#refreshToken,\n      });\n    }\n  }\n\n  /**","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/channels/slack/src/client.ts#L72-L108","documentation":"This is the generic failure branch of Slack token rotation: the API responded with ok:false and an error string that is not the special-cased invalid_refresh_token. The client surfaces Slack's own error verbatim in the message.","triggerScenarios":"#doRotateToken receives { ok: false, error: <some error> } from Slack's rotation endpoint (e.g. expired_refresh_token, invalid_client, rateLimited) during rotateToken().","commonSituations":"Expired refresh tokens after long downtime; wrong client credentials/env in the rotation request; Slack API rate limits or transient outages; malformed stored tokens after manual DB edits.","solutions":["Read the Slack error in the message and act on it (e.g. expired_refresh_token → get fresh tokens from the Slack app config)","Add retry with backoff for transient errors (rateLimited, server errors) in your wrapper around the channel client","Verify the app's client ID/secret and that stored tokens are current; re-install the app if rotation is permanently broken"],"exampleFix":"// before\ntry { await client.rotateToken(); } catch (e) { /* unhandled */ }\n// after\ntry { await client.rotateToken(); } catch (e) {\n  logger.error({ err: e }, 'slack token rotation failed');\n  if (isTransient(e)) await retryWithBackoff(() => client.rotateToken());\n  else await alertOperatorForReinstall();\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await client.rotateToken();\n} catch (e) {\n  const msg = (e as Error).message;\n  if (msg.startsWith('Token rotation failed:') && /rateLimited|temporarily|server/i.test(msg)) {\n    await backoffRetry(() => client.rotateToken(), 3);\n  } else if (msg.startsWith('Slack refresh token is invalid')) {\n    await alertForFreshTokens();\n  } else throw e;\n}","preventionTips":["Wrap rotation with exponential backoff for transient Slack errors","Monitor and log the specific Slack error code from the message","Keep client credentials and stored tokens current","Subscribe to Slack API status/changelog"],"tags":["oauth","slack","token-rotation","api"],"backgroundTag":"oauth-refresh-token-invalid","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}