@@ -128,21 +128,40 @@ async function makeRequest(url, options = {}) {
128128 }
129129}
130130
131- // Retry helper for GitHub API calls
131+ // Retry helper for GitHub API calls (handles transient network errors like "Premature close")
132132async function retryWithBackoff ( fn , maxRetries = 5 , initialDelayMs = 1000 ) {
133133 let attempt = 0
134134 let delay = initialDelayMs
135+ const maxDelayMs = 30000
135136 // eslint-disable-next-line no-constant-condition
136137 while ( true ) {
137138 try {
138139 return await fn ( )
139140 } catch ( error ) {
140- const status = error . status || error . code || error ?. response ?. status
141- if ( attempt < maxRetries && ( status === 403 || status === 429 || ( typeof status === 'number' && status >= 500 ) ) ) {
141+ const status = error ?. status || error ?. response ?. status
142+ const code = error ?. code || error ?. errno
143+ const message = typeof error ?. message === 'string' ? error . message : ''
144+
145+ const isStatusRetriable = status === 403 || status === 429 || ( typeof status === 'number' && status >= 500 )
146+ const isNetworkRetriable =
147+ message . includes ( 'Premature close' ) ||
148+ message . includes ( 'socket hang up' ) ||
149+ message . includes ( 'ECONNRESET' ) ||
150+ message . includes ( 'ETIMEDOUT' ) ||
151+ message . includes ( 'EAI_AGAIN' ) ||
152+ message . includes ( 'network timeout' ) ||
153+ message . includes ( 'NetworkError' ) ||
154+ message . includes ( 'fetch failed' ) ||
155+ message . includes ( 'Invalid response body' )
156+
157+ const shouldRetry = attempt < maxRetries && ( isStatusRetriable || isNetworkRetriable )
158+
159+ if ( shouldRetry ) {
142160 attempt += 1
143- console . warn ( `Retrying after error ${ status } (attempt ${ attempt } /${ maxRetries } )...` )
144- await new Promise ( ( r ) => setTimeout ( r , delay ) )
145- delay *= 2
161+ const jitter = Math . floor ( Math . random ( ) * 250 )
162+ console . warn ( `Retrying after error ${ status || code || message } (attempt ${ attempt } /${ maxRetries } )...` )
163+ await new Promise ( ( r ) => setTimeout ( r , delay + jitter ) )
164+ delay = Math . min ( delay * 2 , maxDelayMs )
146165 continue
147166 }
148167 throw error
@@ -152,8 +171,10 @@ async function retryWithBackoff(fn, maxRetries = 5, initialDelayMs = 1000) {
152171
153172function ghHeaders ( ) {
154173 return {
155- 'Accept' : 'application/vnd.github.v3+json' ,
156- 'Authorization' : `token ${ parsedGithubToken } `
174+ 'Accept' : 'application/vnd.github+json' ,
175+ 'Authorization' : `Bearer ${ parsedGithubToken } ` ,
176+ 'User-Agent' : 'mesh-gov-stats-action/1.0 (+https://gov.meshjs.dev)' ,
177+ 'X-GitHub-Api-Version' : '2022-11-28'
157178 }
158179}
159180
0 commit comments