calcom/cal.diy · error · Error

Invalid refreshed tokens were returned

Error message

Invalid refreshed tokens were returned

What it means

Thrown by parseRefreshTokenResponse when the refreshed token object fails its Zod schema validation. In credential-sync mode it validates against minimumTokenResponseSchema (requires access_token plus at least one numeric expiry-ish field); otherwise against the app-specific schema passed in. A failure means the provider (or sync server) returned a shape that does not match the expected token contract.

Source

Thrown at packages/app-store/_utils/oauth/parseRefreshTokenResponse.ts:36

  });

export type ParseRefreshTokenResponse<S extends z.ZodTypeAny> =
  | z.infer<S>
  | z.infer<typeof minimumTokenResponseSchema>;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const parseRefreshTokenResponse = (response: any, schema: z.ZodTypeAny) => {
  let refreshTokenResponse;
  const credentialSyncingEnabled =
    APP_CREDENTIAL_SHARING_ENABLED && process.env.CALCOM_CREDENTIAL_SYNC_ENDPOINT;
  if (APP_CREDENTIAL_SHARING_ENABLED && process.env.CALCOM_CREDENTIAL_SYNC_ENDPOINT) {
    refreshTokenResponse = minimumTokenResponseSchema.safeParse(response);
  } else {
    refreshTokenResponse = schema.safeParse(response);
  }

  if (!refreshTokenResponse.success) {
    throw new Error("Invalid refreshed tokens were returned");
  }

  if (!refreshTokenResponse.data.refresh_token && credentialSyncingEnabled) {
    refreshTokenResponse.data.refresh_token = "refresh_token";
  }

  return refreshTokenResponse.data;
};

export default parseRefreshTokenResponse;

View on GitHub (pinned to 176037d0af)

Solutions

  1. Log the raw response and the Zod issues (the caller already logs oauth2response/issues) to see which field failed.
  2. If the provider changed its token shape, update the app's token schema accordingly.
  3. Ensure the credential-sync server returns at least access_token and a numeric expiry field.
  4. Confirm the refresh succeeded (a 401/invalid_grant error body parsed as a token will fail validation).

Example fix

// before - schema rejects provider response that uses 'expires_at' string
const schema = z.object({ access_token: z.string(), expires_in: z.number() });
// after - accept the provider's actual shape
const schema = z.object({
  access_token: z.string(),
  expires_in: z.number().optional(),
  expires_at: z.string().optional(),
});
Defensive patterns

Strategy: try-catch

Validate before calling

const refreshTokenResponse = schema.safeParse(response);
if (!refreshTokenResponse.success) {
  // log issues before throwing so the cause is diagnosable
  console.error('Token schema failure', refreshTokenResponse.error.issues, response);
}

Type guard

function isValidTokenResponse<T extends z.ZodTypeAny>(resp: unknown, schema: T): resp is z.infer<T> {
  return schema.safeParse(resp).success;
}

Try / catch

try {
  return parseRefreshTokenResponse(response, schema);
} catch (e) {
  // re-fetch or invalidate the token object; surface a reconnect prompt to the user
  throw new Error('OAuth token refresh returned an invalid shape; re-authorization may be required.');
}

Prevention

When it happens

Trigger: The token-refresh response is missing access_token, missing any numeric expiry field (in sync mode), or does not match the app's specific token schema (e.g. missing expires_in/refresh_token that the app requires). Happens when the provider changes its token format, returns an error body shaped like a token, or the sync server returns a partial payload.

Common situations: See trigger scenarios.

Related errors


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