{"record":{"id":"eefb889bc277bd88","repo":"yikart/AiToEarn","slug":"channelrefreshtokenfailed","errorCode":"ChannelRefreshTokenFailed","errorMessage":"ResponseCode.ChannelRefreshTokenFailed","messagePattern":"ResponseCode\\.ChannelRefreshTokenFailed","errorType":"exception","errorClass":"BilibiliPlatformException","httpStatus":null,"severity":"error","filePath":"project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/bilibili/bilibili.service.ts","lineNumber":123,"sourceCode":"    refreshToken: string\n    expiresAt: Date\n  }> {\n    const body = new URLSearchParams({\n      client_id: this.cfg.clientId,\n      client_secret: this.cfg.clientSecret,\n      grant_type: BilibiliOAuthGrantType.RefreshToken,\n      refresh_token: refreshToken,\n    })\n    const response = await this.platformHttp.post<BilibiliApiResponse<{\n      access_token: string\n      refresh_token: string\n      expires_in: number\n    }>>('https://api.bilibili.com/x/account-oauth2/v1/refresh_token', body.toString(), {\n      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n    })\n\n    if (!response.data.data) {\n      throw new BilibiliPlatformException({\n        code: ResponseCode.ChannelRefreshTokenFailed,\n        category: PlatformErrorCategory.Auth,\n        context: { endpoint: 'POST https://api.bilibili.com/x/account-oauth2/v1/refresh_token' },\n        cause: { type: PlatformErrorCauseType.Platform },\n      })\n    }\n\n    const { access_token, refresh_token, expires_in } = response.data.data\n\n    return {\n      accessToken: access_token,\n      refreshToken: refresh_token,\n      expiresAt: new Date(Date.now() + expires_in * 1000),\n    }\n  }\n\n  async revokeToken(accessToken: string): Promise<void> {\n    // Bilibili Open Platform does not expose a server-side revoke endpoint for this OAuth token.","sourceCodeStart":105,"sourceCodeEnd":141,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/bilibili/bilibili.service.ts#L105-L141","documentation":"BilibiliService.refreshAccessToken POSTs to /x/account-oauth2/v1/refresh_token to rotate tokens. If the response contains no data it throws BilibiliPlatformException ChannelRefreshTokenFailed (category Auth, cause Platform) — Bilibili refused the refresh.","triggerScenarios":"Credential refresh (getUserAccessToken path) sends a refreshToken that Bilibili rejects: token expired/revoked, already used (single-use rotation), or invalid for the app credentials — the endpoint replies without data.","commonSituations":"Refresh token expired (Bilibili refresh tokens have limited validity); refresh token consumed in a prior refresh but the new one was not persisted; user revoked the app; account password change invalidates tokens.","solutions":["Persist the NEW refresh token returned by every refresh call — losing it breaks subsequent refreshes.","On ChannelRefreshTokenFailed, fall back to a full re-authorization (OAuth) for that account.","Check the stored refreshToken is current and not already consumed; restore from logs/backups if rotated incorrectly.","Log Bilibili's full response to capture its error code (e.g. refresh token expired) for diagnosis."],"exampleFix":"// before\nconst r = await refresh(oldRefreshToken)\n// new refreshToken discarded\n\n// after\nconst r = await refresh(oldRefreshToken)\nawait credentialRepo.update(userId, { refreshToken: r.refreshToken }) // persist rotated token","handlingStrategy":"fallback","validationCode":"if (!credential.refreshToken) {\n  await startReauthorization(credential.userId)\n  return\n}","typeGuard":null,"tryCatchPattern":"try {\n  tokens = await bilibiliService.refreshAccessToken(credential.refreshToken)\n  await credentialRepo.update(userId, { refreshToken: tokens.refreshToken })\n} catch (e) {\n  if (e.code === 'ChannelRefreshTokenFailed') {\n    await markCredentialNeedsReauth(userId) // fallback: full re-auth\n  } else throw e\n}","preventionTips":["Always persist the rotated refresh token returned by each refresh call","Handle refresh failure by scheduling re-authorization, not endless retries","Alert on refresh failures to catch revoked/expired grants early"],"tags":["bilibili","oauth","auth","refresh-token"],"backgroundTag":"refresh-token-failed","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}