@@ -311,6 +311,31 @@ async function fetchIndividualModule(module: EnrichedModule, repoType: string, c
311311 }
312312}
313313
314+ /**
315+ * Public fallback for unauthenticated GitHub API rate limits.
316+ * GitHub's commits Atom feed is typically still reachable when REST API returns 403.
317+ */
318+ async function fetchGitHubLastCommitFromAtom ( moduleUrl : string , client : HttpClient ) : Promise < string | null > {
319+ const repoId = getRepositoryId ( moduleUrl ) ;
320+ if ( ! repoId ) {
321+ return null ;
322+ }
323+
324+ try {
325+ const atomUrl = `https://github.com/${ repoId } /commits.atom` ;
326+ const result = await client . getText ( atomUrl ) as FetchTextResult ;
327+ if ( ! result . ok ) {
328+ return null ;
329+ }
330+
331+ const updatedMatch = result . data . match ( / < u p d a t e d > ( [ ^ < ] + ) < \/ u p d a t e d > / u) ;
332+ return updatedMatch ?. [ 1 ] ?? null ;
333+ }
334+ catch {
335+ return null ;
336+ }
337+ }
338+
314339async function appendGitHubBatchResult ( {
315340 module,
316341 batchResults,
@@ -430,6 +455,25 @@ async function processModule(module: EnrichedModule, context: ProcessModuleConte
430455 } ;
431456 }
432457
458+ if ( repoType === "github" && ! process . env . GITHUB_TOKEN && recovery . error . message . includes ( "403" ) ) {
459+ const lastCommitFromAtom = await fetchGitHubLastCommitFromAtom ( module . url , client ) ;
460+ if ( lastCommitFromAtom ) {
461+ const atomRecovery : CachedRepositoryValue = {
462+ lastCommit : lastCommitFromAtom ,
463+ isArchived : typeof module . isArchived === "boolean" ? module . isArchived : undefined ,
464+ hasGithubIssues : typeof module . hasGithubIssues === "boolean" ? module . hasGithubIssues : undefined
465+ } ;
466+
467+ repositoryCache . set ( repoId , atomRecovery ) ;
468+ logger . info ( `Recovered ${ module . name } lastCommit via GitHub Atom fallback` ) ;
469+
470+ return {
471+ ...module ,
472+ ...atomRecovery
473+ } ;
474+ }
475+ }
476+
433477 logger . warn ( `Failed to fetch metadata for ${ module . name } ` , { url : module . url , error : recovery . error . message } ) ;
434478 circuitBreaker . recordError ( recovery . error ) ;
435479
0 commit comments