calcom/cal.diy · critical · Error

Feishu Calendar refresh token expired

Error message

Feishu Calendar refresh token expired

What it means

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.

Source

Thrown at packages/app-store/feishucalendar/lib/CalendarService.ts:65

  }

  private feishuAuth = (credential: CredentialPayload) => {
    const feishuAuthCredentials = credential.key as FeishuAuthCredentials;
    return {
      getToken: () =>
        !isExpired(feishuAuthCredentials.expiry_date)
          ? Promise.resolve(feishuAuthCredentials.access_token)
          : this.refreshAccessToken(credential),
    };
  };

  private refreshAccessToken = async (credential: CredentialPayload) => {
    const feishuAuthCredentials = credential.key as FeishuAuthCredentials;
    const refreshExpireDate = feishuAuthCredentials.refresh_expires_date;
    const refreshToken = feishuAuthCredentials.refresh_token;
    if (isExpired(refreshExpireDate) || !refreshToken) {
      await prisma.credential.delete({ where: { id: credential.id } });
      throw new Error("Feishu Calendar refresh token expired");
    }
    try {
      const appAccessToken = await getAppAccessToken();
      const resp = await refreshOAuthTokens(
        async () =>
          await fetch(`${this.url}/authen/v1/refresh_access_token`, {
            method: "POST",
            headers: {
              Authorization: `Bearer ${appAccessToken}`,
              "Content-Type": "application/json; charset=utf-8",
            },
            body: JSON.stringify({
              grant_type: "refresh_token",
              refresh_token: refreshToken,
            }),
          }),
        "feishu-calendar",
        credential.userId

View on GitHub (pinned to 176037d0af)

Solutions

  1. Re-authenticate the Feishu calendar integration: the credential has already been deleted, so the user must reinstall it from /apps/feishucalendar.
  2. Verify isExpired() expects the same time unit (seconds since epoch) as refresh_expires_date to avoid false positives.
  3. Schedule a periodic token refresh before refresh_expires_date to keep the credential alive.
  4. Confirm the OAuth install flow stores refresh_token and refresh_expires_date correctly.

Example fix

// before - hard delete + throw
if (isExpired(refreshExpireDate) || !refreshToken) {
  await prisma.credential.delete({ where: { id: credential.id } });
  throw new Error("Feishu Calendar refresh token expired");
}

// after - mark invalid instead of deleting, so audit trail remains
if (isExpired(refreshExpireDate) || !refreshToken) {
  await prisma.credential.update({ where: { id: credential.id }, data: { invalid: true } });
  throw new Error("Feishu Calendar refresh token expired; please reconnect the integration.");
}
Defensive patterns

Strategy: try-catch

Validate before calling

const cred = await prisma.credential.findUnique({ where: { id: credentialId } });
const key = cred?.key as FeishuAuthCredentials | undefined;
if (!key?.refresh_token || isExpired(key.refresh_expires_date)) {
  throw new Error("Feishu refresh token expired or missing; user must reconnect.");
}

Type guard

const hasValidFeishuRefresh = (k: unknown): k is FeishuAuthCredentials =>
  typeof k === "object" && k !== null &&
  typeof (k as any).refresh_token === "string" && (k as any).refresh_token.length > 0 &&
  !isExpired((k as any).refresh_expires_date);

Try / catch

try {
  await calendarService.createEvent(event, credentialId);
} catch (err) {
  if (err instanceof Error && /refresh token expired/i.test(err.message)) {
    // credential was deleted; prompt reconnect
    await notifyUserReconnectFeishu(userId);
  }
  throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of calcom/cal.diy@176037d0af (2026-08-12). Data as JSON: /api/errors/9ba7cfd6f80d1e9f. Report an issue: GitHub.