@@ -251,7 +251,10 @@ export async function upsertIssuesFromWorkItems(
251251 } ;
252252
253253 // Concurrency: upsert issues and comments with a bounded concurrency pool
254- const upsertConcurrency = Number ( process . env . WL_GITHUB_CONCURRENCY || '6' ) ;
254+ // The central throttler enforces concurrency/rate limits. Do not rely on
255+ // a local worker pool here; schedule GitHub API calls through `throttler`.
256+ // Keep the env var available to the throttler implementation.
257+ // (local upsertConcurrency removed)
255258
256259 const truncateTitle = ( title : string , maxLen = 60 ) : string =>
257260 title . length <= maxLen ? title : title . slice ( 0 , maxLen - 1 ) + '\u2026' ;
@@ -292,14 +295,16 @@ export async function upsertIssuesFromWorkItems(
292295 const shouldUpdateIssue = ! item . githubIssueNumber
293296 || ! item . githubIssueUpdatedAt
294297 || new Date ( item . updatedAt ) . getTime ( ) > new Date ( item . githubIssueUpdatedAt ) . getTime ( ) ;
295- if ( shouldUpdateIssue ) {
298+ if ( shouldUpdateIssue ) {
296299 const upsertStart = Date . now ( ) ;
297300 if ( onVerboseLog ) {
298301 onVerboseLog ( `[upsert] ${ item . githubIssueNumber ? 'update' : 'create' } ${ item . id } ` ) ;
299302 }
300- if ( item . githubIssueNumber ) {
301- increment ( 'api.issue.update' ) ;
302- issue = await updateGithubIssueAsync ( config , item . githubIssueNumber ! , payload ) ;
303+ if ( item . githubIssueNumber ) {
304+ increment ( 'api.issue.update' ) ;
305+ // updateGithubIssueAsync already schedules via the central throttler
306+ // internally (see src/github.ts). Avoid double-scheduling here.
307+ issue = await updateGithubIssueAsync ( config , item . githubIssueNumber ! , payload ) ;
303308 if ( item . status === 'deleted' ) {
304309 result . closed += 1 ;
305310 result . syncedItems . push ( {
@@ -317,13 +322,14 @@ export async function upsertIssuesFromWorkItems(
317322 issueNumber : item . githubIssueNumber ,
318323 } ) ;
319324 }
320- } else {
321- increment ( 'api.issue.create' ) ;
322- issue = await createGithubIssueAsync ( config , {
323- title : payload . title ,
324- body : payload . body ,
325- labels : payload . labels ,
326- } ) ;
325+ } else {
326+ increment ( 'api.issue.create' ) ;
327+ // createGithubIssueAsync schedules via the central throttler itself.
328+ issue = await createGithubIssueAsync ( config , {
329+ title : payload . title ,
330+ body : payload . body ,
331+ labels : payload . labels ,
332+ } ) ;
327333 result . created += 1 ;
328334 result . syncedItems . push ( {
329335 action : 'created' ,
@@ -343,14 +349,16 @@ export async function upsertIssuesFromWorkItems(
343349 }
344350
345351 const shouldSyncCommentsNow = itemComments . length > 0 && ( shouldSyncComments || shouldUpdateIssue ) ;
346- if ( shouldSyncCommentsNow && issueNumber ) {
347- const commentListStart = Date . now ( ) ;
348- increment ( 'api.comment.list' ) ;
349- const existingComments = await listGithubIssueCommentsAsync ( config , issueNumber ! ) ;
350- timing . commentListMs += Date . now ( ) - commentListStart ;
351- const commentUpsertStart = Date . now ( ) ;
352- const commentSummary = await upsertGithubIssueCommentsAsync ( config , issueNumber , itemComments , existingComments ) ;
353- timing . commentUpsertMs += Date . now ( ) - commentUpsertStart ;
352+ if ( shouldSyncCommentsNow && issueNumber ) {
353+ const commentListStart = Date . now ( ) ;
354+ increment ( 'api.comment.list' ) ;
355+ // listGithubIssueCommentsAsync now schedules internally via the throttler
356+ // (see src/github.ts). Call it directly to avoid double-scheduling.
357+ const existingComments = await listGithubIssueCommentsAsync ( config , issueNumber ! ) ;
358+ timing . commentListMs += Date . now ( ) - commentListStart ;
359+ const commentUpsertStart = Date . now ( ) ;
360+ const commentSummary = await upsertGithubIssueCommentsAsync ( config , issueNumber , itemComments , existingComments ) ;
361+ timing . commentUpsertMs += Date . now ( ) - commentUpsertStart ;
354362 increment ( 'api.comment.create' , commentSummary . created || 0 ) ;
355363 increment ( 'api.comment.update' , commentSummary . updated || 0 ) ;
356364 result . commentsCreated = ( result . commentsCreated || 0 ) + commentSummary . created ;
@@ -399,12 +407,14 @@ export async function upsertIssuesFromWorkItems(
399407 for ( const comment of sorted ) {
400408 const body = buildGithubCommentBody ( comment ) ;
401409 const existing = byWorklogId . get ( comment . id ) ;
402- if ( existing ) {
410+ if ( existing ) {
403411 // If the GH comment exists, only update if body changed OR GH's updatedAt is newer than our recorded mapping
404412 const bodyMatch = ( existing . body || '' ) . trim ( ) === body . trim ( ) ;
405- if ( ! bodyMatch ) {
406- increment ( 'api.comment.update' ) ;
407- const updatedComment = await updateGithubIssueCommentAsync ( issueConfig , existing . id ! , body ) ;
413+ if ( ! bodyMatch ) {
414+ increment ( 'api.comment.update' ) ;
415+ // updateGithubIssueCommentAsync now schedules internally via the throttler
416+ // (see src/github.ts). Call it directly to avoid double-scheduling.
417+ const updatedComment = await updateGithubIssueCommentAsync ( issueConfig , existing . id ! , body ) ;
408418 // Persist mapping back to local comment
409419 comment . githubCommentId = existing . id ;
410420 comment . githubCommentUpdatedAt = updatedComment . updatedAt ;
@@ -417,9 +427,11 @@ export async function upsertIssuesFromWorkItems(
417427 continue ;
418428 }
419429
420- // No GH comment mapping found — create a new comment
421- increment ( 'api.comment.create' ) ;
422- const createdComment = await createGithubIssueCommentAsync ( issueConfig , issueNumber , body ) ;
430+ // No GH comment mapping found — create a new comment
431+ increment ( 'api.comment.create' ) ;
432+ // createGithubIssueCommentAsync now schedules internally via the throttler
433+ // (see src/github.ts). Call it directly to avoid double-scheduling.
434+ const createdComment = await createGithubIssueCommentAsync ( issueConfig , issueNumber , body ) ;
423435 // Persist mapping back to local comment so future runs can directly reference by ID
424436 comment . githubCommentId = createdComment . id ;
425437 comment . githubCommentUpdatedAt = createdComment . updatedAt ;
@@ -433,23 +445,10 @@ export async function upsertIssuesFromWorkItems(
433445 return { created, updated, latestUpdatedAt } ;
434446 }
435447
436- // simple concurrent mapper for issue upserts
437- async function mapWithConcurrencyItems ( arr : WorkItem [ ] , limit : number , fn : ( v : WorkItem , i : number ) => Promise < void > ) {
438- const results : Promise < void > [ ] = [ ] ;
439- let i = 0 ;
440- async function worker ( ) {
441- while ( true ) {
442- const idx = i ++ ;
443- if ( idx >= arr . length ) return ;
444- await fn ( arr [ idx ] , idx ) ;
445- }
446- }
447- const workers = Math . min ( limit , arr . length ) ;
448- for ( let w = 0 ; w < workers ; w += 1 ) results . push ( worker ( ) ) ;
449- await Promise . all ( results ) ;
450- }
451-
452- await mapWithConcurrencyItems ( issueItems , upsertConcurrency , upsertMapper ) ;
448+ // Launch upsert mappers without a local worker pool; schedule external
449+ // GitHub API calls through the central throttler. The throttler enforces
450+ // WL_GITHUB_CONCURRENCY and rate limits configured in src/github-throttler.ts.
451+ await Promise . all ( issueItems . map ( ( it , idx ) => upsertMapper ( it , idx ) ) ) ;
453452
454453 result . skipped = items . length - issueItems . length + skippedUpdates ;
455454
@@ -554,23 +553,10 @@ export async function upsertIssuesFromWorkItems(
554553 }
555554 }
556555
557- // simple concurrent mapper
558- async function mapWithConcurrency ( arr : string [ ] , limit : number , fn : ( v : string , i : number ) => Promise < void > ) {
559- const results : Promise < void > [ ] = [ ] ;
560- let i = 0 ;
561- async function worker ( ) {
562- while ( true ) {
563- const idx = i ++ ;
564- if ( idx >= arr . length ) return ;
565- await fn ( arr [ idx ] , idx ) ;
566- }
567- }
568- const workers = Math . min ( limit , arr . length ) ;
569- for ( let w = 0 ; w < workers ; w += 1 ) results . push ( worker ( ) ) ;
570- await Promise . all ( results ) ;
571- }
572-
573- await mapWithConcurrency ( pairs , concurrency , mapper ) ;
556+ // Process hierarchy pairs concurrently and let the throttler limit GitHub
557+ // requests. Avoid a local worker pool — schedule linking/fetch calls via
558+ // the central throttler inside `mapper`.
559+ await Promise . all ( pairs . map ( ( p , idx ) => mapper ( p , idx ) ) ) ;
574560
575561 result . updated += linkedCount ;
576562 timing . totalMs = Date . now ( ) - startTime ;
0 commit comments