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,39 @@ 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+ const throttleOptions = {
138+ onRateLimit : ( retryAfter , options , octokit ) => {
139+ warn ( `Primary rate limit exhausted for request ${ options . method } ${ options . url } ` ) ;
140+ if ( options . request . retryCount <= 2 ) {
141+ warn ( `Retrying after ${ retryAfter } seconds (retry ${ options . request . retryCount + 1 } /3)` ) ;
142+ return true ;
143+ }
144+ error ( `Max retries reached for rate limit` ) ;
145+ return false ;
146+ } ,
147+ onSecondaryRateLimit : ( retryAfter , options , octokit ) => {
148+ warn ( `Secondary rate limit detected for request ${ options . method } ${ options . url } ` ) ;
149+ if ( options . request . retryCount <= 2 ) {
150+ warn ( `Retrying after ${ retryAfter } seconds (retry ${ options . request . retryCount + 1 } /3)` ) ;
151+ return true ;
152+ }
153+ error ( `Max retries reached for secondary rate limit` ) ;
154+ return false ;
155+ }
156+ } ;
157+
158+ // Initialize Octokit instances with throttling enabled
138159const sourceOctokit = new Octokit ( {
139160 auth : process . env . SOURCE_TOKEN ,
140- baseUrl : SOURCE_API_URL
161+ baseUrl : SOURCE_API_URL ,
162+ throttle : throttleOptions
141163} ) ;
142164
143165const targetOctokit = new Octokit ( {
144166 auth : process . env . TARGET_TOKEN ,
145- baseUrl : TARGET_API_URL
167+ baseUrl : TARGET_API_URL ,
168+ throttle : throttleOptions
146169} ) ;
147170
148171// Tracking variables
@@ -178,39 +201,6 @@ async function rateLimitSleep(seconds = RATE_LIMIT_SLEEP_SECONDS) {
178201 await sleep ( seconds ) ;
179202}
180203
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-
214204function formatPollData ( poll ) {
215205 if ( ! poll || ! poll . options || poll . options . nodes . length === 0 ) {
216206 return '' ;
@@ -1039,7 +1029,7 @@ async function processDiscussionsPage(sourceOctokit, targetOctokit, owner, repo,
10391029 // Check if discussion is pinned
10401030 const isPinned = pinnedDiscussionIds . has ( discussion . id ) ;
10411031
1042- // Create discussion with retry logic
1032+ // Create discussion (Octokit throttling plugin handles rate limits automatically)
10431033 let newDiscussion = null ;
10441034 let createSuccess = false ;
10451035
@@ -1065,22 +1055,17 @@ async function processDiscussionsPage(sourceOctokit, targetOctokit, owner, repo,
10651055 log ( `✓ Created discussion #${ discussion . number } : '${ discussion . title } '` ) ;
10661056
10671057 } catch ( err ) {
1068- // Handle rate limit errors with retry
1069- const shouldRetry = await handleRateLimitError ( err , attempt ) ;
1058+ // Octokit throttling handles rate limits; this catches other errors
1059+ error ( `Failed to create discussion # ${ discussion . number } : ' ${ discussion . title } ' - ${ err . message } ` ) ;
10701060
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- }
1061+ if ( attempt < MAX_RETRIES ) {
1062+ warn ( `Retrying (attempt ${ attempt + 1 } /${ MAX_RETRIES } ) in 5 seconds...` ) ;
1063+ await sleep ( 5 ) ;
1064+ } else {
1065+ error ( `Max retries (${ MAX_RETRIES } ) reached. Skipping discussion #${ discussion . number } .` ) ;
1066+ skippedDiscussions ++ ;
1067+ break ;
10821068 }
1083- // If shouldRetry is true, loop will continue to next attempt
10841069 }
10851070 }
10861071
0 commit comments