3636const INCLUDE_POLL_MERMAID_CHART = true ; // Set to false to disable Mermaid pie chart for polls
3737const RATE_LIMIT_SLEEP_SECONDS = 0.5 ; // Default sleep duration between API calls to avoid rate limiting
3838const DISCUSSION_PROCESSING_DELAY_SECONDS = 5 ; // Delay between processing discussions
39- const RATE_LIMIT_RETRY_DELAY_SECONDS = 60 ; // Delay when hitting rate limits before retrying
40- const MAX_RETRIES = 3 ; // Maximum number of retries for failed operations
39+ const MAX_RETRIES = 3 ; // Maximum number of retries for failed operations (rate limits handled automatically by Octokit)
4140
4241const { Octokit } = require ( "octokit" ) ;
4342
@@ -134,15 +133,41 @@ if (!process.env.TARGET_TOKEN) {
134133const SOURCE_API_URL = process . env . SOURCE_API_URL || 'https://api.github.com' ;
135134const TARGET_API_URL = process . env . TARGET_API_URL || 'https://api.github.com' ;
136135
137- // Initialize Octokit instances
136+ // Configure throttling for rate limit handling
137+ // Octokit's throttling plugin automatically handles both REST and GraphQL rate limits
138+ // by intercepting HTTP 403 responses and retry-after headers
139+ const throttleOptions = {
140+ onRateLimit : ( retryAfter , options , octokit ) => {
141+ warn ( `Primary rate limit exhausted for request ${ options . method } ${ options . url } ` ) ;
142+ if ( options . request . retryCount <= 2 ) {
143+ warn ( `Retrying after ${ retryAfter } seconds (retry ${ options . request . retryCount + 1 } /3)` ) ;
144+ return true ;
145+ }
146+ error ( `Max retries reached for rate limit` ) ;
147+ return false ;
148+ } ,
149+ onSecondaryRateLimit : ( retryAfter , options , octokit ) => {
150+ warn ( `Secondary rate limit detected for request ${ options . method } ${ options . url } ` ) ;
151+ if ( options . request . retryCount <= 2 ) {
152+ warn ( `Retrying after ${ retryAfter } seconds (retry ${ options . request . retryCount + 1 } /3)` ) ;
153+ return true ;
154+ }
155+ error ( `Max retries reached for secondary rate limit` ) ;
156+ return false ;
157+ }
158+ } ;
159+
160+ // Initialize Octokit instances with throttling enabled
138161const sourceOctokit = new Octokit ( {
139162 auth : process . env . SOURCE_TOKEN ,
140- baseUrl : SOURCE_API_URL
163+ baseUrl : SOURCE_API_URL ,
164+ throttle : throttleOptions
141165} ) ;
142166
143167const targetOctokit = new Octokit ( {
144168 auth : process . env . TARGET_TOKEN ,
145- baseUrl : TARGET_API_URL
169+ baseUrl : TARGET_API_URL ,
170+ throttle : throttleOptions
146171} ) ;
147172
148173// Tracking variables
@@ -178,39 +203,6 @@ async function rateLimitSleep(seconds = RATE_LIMIT_SLEEP_SECONDS) {
178203 await sleep ( seconds ) ;
179204}
180205
181- function isRateLimitError ( err ) {
182- const message = err . message ?. toLowerCase ( ) || '' ;
183- const status = err . status || 0 ;
184-
185- // Check for primary rate limit (403 with rate limit message)
186- if ( status === 403 && ( message . includes ( 'rate limit' ) || message . includes ( 'api rate limit' ) ) ) {
187- return true ;
188- }
189-
190- // Check for secondary rate limit (403 with abuse/secondary message)
191- if ( status === 403 && ( message . includes ( 'secondary' ) || message . includes ( 'abuse' ) ) ) {
192- return true ;
193- }
194-
195- // Check for retry-after header indication
196- if ( message . includes ( 'retry after' ) || message . includes ( 'try again later' ) ) {
197- return true ;
198- }
199-
200- return false ;
201- }
202-
203- async function handleRateLimitError ( err , attemptNumber ) {
204- if ( isRateLimitError ( err ) ) {
205- const waitTime = RATE_LIMIT_RETRY_DELAY_SECONDS * attemptNumber ; // Exponential-ish backoff
206- warn ( `Rate limit detected (attempt ${ attemptNumber } ). Waiting ${ waitTime } s before retry...` ) ;
207- warn ( `Error details: ${ err . message } ` ) ;
208- await sleep ( waitTime ) ;
209- return true ;
210- }
211- return false ;
212- }
213-
214206function formatPollData ( poll ) {
215207 if ( ! poll || ! poll . options || poll . options . nodes . length === 0 ) {
216208 return '' ;
@@ -1039,7 +1031,7 @@ async function processDiscussionsPage(sourceOctokit, targetOctokit, owner, repo,
10391031 // Check if discussion is pinned
10401032 const isPinned = pinnedDiscussionIds . has ( discussion . id ) ;
10411033
1042- // Create discussion with retry logic
1034+ // Create discussion (Octokit throttling plugin handles rate limits automatically)
10431035 let newDiscussion = null ;
10441036 let createSuccess = false ;
10451037
@@ -1065,22 +1057,17 @@ async function processDiscussionsPage(sourceOctokit, targetOctokit, owner, repo,
10651057 log ( `✓ Created discussion #${ discussion . number } : '${ discussion . title } '` ) ;
10661058
10671059 } catch ( err ) {
1068- // Handle rate limit errors with retry
1069- const shouldRetry = await handleRateLimitError ( err , attempt ) ;
1060+ // Octokit throttling handles rate limits; this catches other errors
1061+ error ( `Failed to create discussion # ${ discussion . number } : ' ${ discussion . title } ' - ${ err . message } ` ) ;
10701062
1071- if ( ! shouldRetry ) {
1072- // Not a rate limit error, or unrecoverable error
1073- error ( `Failed to create discussion #${ discussion . number } : '${ discussion . title } ' - ${ err . message } ` ) ;
1074- if ( attempt < MAX_RETRIES ) {
1075- warn ( `Retrying (attempt ${ attempt + 1 } /${ MAX_RETRIES } )...` ) ;
1076- await sleep ( 5 ) ; // Brief pause before retry
1077- } else {
1078- error ( `Max retries (${ MAX_RETRIES } ) reached. Skipping discussion #${ discussion . number } .` ) ;
1079- skippedDiscussions ++ ;
1080- break ;
1081- }
1063+ if ( attempt < MAX_RETRIES ) {
1064+ warn ( `Retrying (attempt ${ attempt + 1 } /${ MAX_RETRIES } ) in 5 seconds...` ) ;
1065+ await sleep ( 5 ) ;
1066+ } else {
1067+ error ( `Max retries (${ MAX_RETRIES } ) reached. Skipping discussion #${ discussion . number } .` ) ;
1068+ skippedDiscussions ++ ;
1069+ break ;
10821070 }
1083- // If shouldRetry is true, loop will continue to next attempt
10841071 }
10851072 }
10861073
0 commit comments