diff --git a/plugins/notion/server/notion.ts b/plugins/notion/server/notion.ts index 84e96d548b..eaf77bd6dd 100644 --- a/plugins/notion/server/notion.ts +++ b/plugins/notion/server/notion.ts @@ -6,6 +6,7 @@ import { isFullPageOrDatabase, isFullUser, RequestTimeoutError, + UnknownHTTPResponseError, } from "@notionhq/client"; import type { BlockObjectResponse, @@ -60,6 +61,7 @@ export class NotionClient { private limiter: ReturnType; private pageSize = 100; private maxRetries = 3; + private maxServerErrorRetries = 8; private retryDelay = 1000; private skipChildrenForBlock = [ "unsupported", @@ -94,6 +96,7 @@ export class NotionClient { */ private async fetchWithRetry(apiCall: () => Promise): Promise { let retries = 0; + let serverErrorRetries = 0; // oxlint-disable-next-line no-constant-condition while (true) { @@ -149,6 +152,32 @@ export class NotionClient { ); } + // Check if this is a server-side error (5xx) — Notion's API can be + // unreliable, so retry these for longer with exponential backoff. + if ( + (error instanceof APIResponseError || + error instanceof UnknownHTTPResponseError) && + error.status >= 500 + ) { + if (serverErrorRetries < this.maxServerErrorRetries) { + serverErrorRetries++; + const delay = this.retryDelay * 2 ** (serverErrorRetries - 1); + Logger.info( + "task", + `Notion API returned ${error.status}, retrying in ${delay}ms (retry ${serverErrorRetries}/${this.maxServerErrorRetries})` + ); + + // Wait before retrying + await new Promise((resolve) => setTimeout(resolve, delay)); + continue; + } + + Logger.warn( + `Notion API returned ${error.status} after ${this.maxServerErrorRetries} retries`, + { error: error.message } + ); + } + // Re-throw the error if it's not a rate limit issue or we've exhausted retries throw error; }