{"record":{"id":"9ba7cfd6f80d1e9f","repo":"calcom/cal.diy","slug":"feishu-calendar-refresh-token-expired","errorCode":null,"errorMessage":"Feishu Calendar refresh token expired","messagePattern":"Feishu Calendar refresh token expired","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"packages/app-store/feishucalendar/lib/CalendarService.ts","lineNumber":65,"sourceCode":"  }\n\n  private feishuAuth = (credential: CredentialPayload) => {\n    const feishuAuthCredentials = credential.key as FeishuAuthCredentials;\n    return {\n      getToken: () =>\n        !isExpired(feishuAuthCredentials.expiry_date)\n          ? Promise.resolve(feishuAuthCredentials.access_token)\n          : this.refreshAccessToken(credential),\n    };\n  };\n\n  private refreshAccessToken = async (credential: CredentialPayload) => {\n    const feishuAuthCredentials = credential.key as FeishuAuthCredentials;\n    const refreshExpireDate = feishuAuthCredentials.refresh_expires_date;\n    const refreshToken = feishuAuthCredentials.refresh_token;\n    if (isExpired(refreshExpireDate) || !refreshToken) {\n      await prisma.credential.delete({ where: { id: credential.id } });\n      throw new Error(\"Feishu Calendar refresh token expired\");\n    }\n    try {\n      const appAccessToken = await getAppAccessToken();\n      const resp = await refreshOAuthTokens(\n        async () =>\n          await fetch(`${this.url}/authen/v1/refresh_access_token`, {\n            method: \"POST\",\n            headers: {\n              Authorization: `Bearer ${appAccessToken}`,\n              \"Content-Type\": \"application/json; charset=utf-8\",\n            },\n            body: JSON.stringify({\n              grant_type: \"refresh_token\",\n              refresh_token: refreshToken,\n            }),\n          }),\n        \"feishu-calendar\",\n        credential.userId","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/calcom/cal.diy/blob/176037d0afbe572f870a3c702985e7cd83fe6c0c/packages/app-store/feishucalendar/lib/CalendarService.ts#L47-L83","documentation":"Thrown by FeishuCalendarService.refreshAccessToken when the stored refresh_token has expired or is missing. Refresh tokens in Feishu have a refresh_expires_date; if that date has passed or the token field is absent, the credential is unrecoverable so the handler DELETES the credential from the DB and throws, forcing the user to re-authenticate.","triggerScenarios":"Calling any calendar operation that needs a fresh access token after refresh_expires_date has elapsed, or when feishuAuthCredentials.refresh_token was never stored (incomplete OAuth install). The credential row is removed as a side effect.","commonSituations":"User has not used Feishu calendar for > 30 days (refresh token expired); OAuth install was interrupted before refresh_token persisted; refresh_expires_date stored in wrong unit (seconds vs ms) causing premature expiry.","solutions":["Re-authenticate the Feishu calendar integration: the credential has already been deleted, so the user must reinstall it from /apps/feishucalendar.","Verify isExpired() expects the same time unit (seconds since epoch) as refresh_expires_date to avoid false positives.","Schedule a periodic token refresh before refresh_expires_date to keep the credential alive.","Confirm the OAuth install flow stores refresh_token and refresh_expires_date correctly."],"exampleFix":"// before - hard delete + throw\nif (isExpired(refreshExpireDate) || !refreshToken) {\n  await prisma.credential.delete({ where: { id: credential.id } });\n  throw new Error(\"Feishu Calendar refresh token expired\");\n}\n\n// after - mark invalid instead of deleting, so audit trail remains\nif (isExpired(refreshExpireDate) || !refreshToken) {\n  await prisma.credential.update({ where: { id: credential.id }, data: { invalid: true } });\n  throw new Error(\"Feishu Calendar refresh token expired; please reconnect the integration.\");\n}","handlingStrategy":"try-catch","validationCode":"const cred = await prisma.credential.findUnique({ where: { id: credentialId } });\nconst key = cred?.key as FeishuAuthCredentials | undefined;\nif (!key?.refresh_token || isExpired(key.refresh_expires_date)) {\n  throw new Error(\"Feishu refresh token expired or missing; user must reconnect.\");\n}","typeGuard":"const hasValidFeishuRefresh = (k: unknown): k is FeishuAuthCredentials =>\n  typeof k === \"object\" && k !== null &&\n  typeof (k as any).refresh_token === \"string\" && (k as any).refresh_token.length > 0 &&\n  !isExpired((k as any).refresh_expires_date);","tryCatchPattern":"try {\n  await calendarService.createEvent(event, credentialId);\n} catch (err) {\n  if (err instanceof Error && /refresh token expired/i.test(err.message)) {\n    // credential was deleted; prompt reconnect\n    await notifyUserReconnectFeishu(userId);\n  }\n  throw err;\n}","preventionTips":["Background-refresh Feishu tokens well before refresh_expires_date.","Verify refresh_expires_date is stored in the same unit isExpired() expects.","Notify users proactively when their Feishu connection nears refresh-token expiry."],"tags":["feishu","larksuite","refresh-token","oauth","credential","irreversible"],"backgroundTag":null,"analyzedSha":"176037d0afbe572f870a3c702985e7cd83fe6c0c","analyzedAt":"2026-08-12T19:12:41.464Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}