diff --git a/src/queue/processors.ts b/src/queue/processors.ts index df986f7359..8db1c30782 100644 --- a/src/queue/processors.ts +++ b/src/queue/processors.ts @@ -1462,7 +1462,7 @@ async function refreshOpenPullRequestsForScheduledSweep( // surfaceRepairPriorityPullNumbers has no memory of prior attempts -- if the repair keeps failing for the SAME // head SHA (e.g. every AI-provider attempt times out), it would otherwise re-select that PR forever, burning a // fresh review attempt every cycle for zero output. These two constants cap that: once a SHA has already had -const REGATE_REPAIR_MAX_ATTEMPTS_PER_SHA = 5; +// REGATE_REPAIR_MAX_ATTEMPTS_PER_SHA dispatches recorded, it drops back to ordinary staleness-gated candidacy // (still eventually re-checked, just not on every tick) and a single REGATE_REPAIR_EXHAUSTED_EVENT_TYPE audit // event is recorded so the stuck PR is visible instead of silently retried forever. A new commit changes the // head SHA, which resets the count naturally (the target key is scoped to repo+PR+SHA). @@ -1871,21 +1871,21 @@ async function sweepRepoRegate( const job: JobMessage = { type: "agent-regate-pr", deliveryId: isPriorityRepair - // #orb-retry-storm: pass the repair SHA so regatePullRequest can record the attempt at - // execution time (after rate-limit admission), not here at dispatch time. Jobs that are - // deferred or dropped before they run no longer count against the per-SHA cap. - ...(isPriorityRepair && pr.headSha ? { repairHeadSha: pr.headSha } : {}), ? `regate-repair:${repoFullName}#${pr.number}` : `regate-sweep:${repoFullName}#${pr.number}`, repoFullName, prNumber: pr.number, installationId: sweepInstallationId, - targetKey: regateRepairTargetKey(repoFullName, pr.number, pr.headSha), - outcome: "queued", - detail: `outage-repair re-review dispatched for ${repoFullName}#${pr.number}`, - metadata: { repoFullName, prNumber: pr.number, headSha: pr.headSha }, - }); - } + // #orb-retry-storm: pass the repair SHA so regatePullRequest can record the attempt at + // execution time (after rate-limit admission), not here at dispatch time. Jobs that are + // deferred or dropped before they run no longer count against the per-SHA cap. + ...(isPriorityRepair && pr.headSha ? { repairHeadSha: pr.headSha } : {}), + ...(pr.createdAt ? { prCreatedAt: pr.createdAt } : {}), + }; + const delaySeconds = Math.min(index * 10, 600); + await (delaySeconds > 0 + ? env.JOBS.send(job, { delaySeconds }) + : env.JOBS.send(job)); } } await recordAuditEvent(env, { @@ -2089,7 +2089,7 @@ async function sweepRepoBacklogConvergence( // per-PR job always re-evaluates the head. async function regatePullRequest( env: Env, - repairHeadSha?: string, + repairHeadSha: string | undefined, repoFullName: string, prNumber: number, installationId: number, @@ -2122,6 +2122,12 @@ async function regatePullRequest( repoFullName, prNumber, installationId, + ...(prCreatedAt ? { prCreatedAt } : {}), + ...(force ? { force: true } : {}), + }, + { delaySeconds: delayUntil(rateResetAt) }, + ); + return; } // #orb-retry-storm: record the repair attempt NOW — after rate-limit admission — so the cap in // surfaceRepairPriorityPullNumbers counts actual executions, not queued dispatches that may have @@ -2132,16 +2138,10 @@ async function regatePullRequest( eventType: REGATE_REPAIR_ATTEMPT_EVENT_TYPE, actor: "gittensory", targetKey: regateRepairTargetKey(repoFullName, prNumber, repairHeadSha), - outcome: "started", + outcome: "completed", detail: `outage-repair re-review executing for ${repoFullName}#${prNumber}`, metadata: { repoFullName, prNumber, headSha: repairHeadSha }, }); - ...(prCreatedAt ? { prCreatedAt } : {}), - ...(force ? { force: true } : {}), - }, - { delaySeconds: delayUntil(rateResetAt) }, - ); - return; } const settings = await resolveRepositorySettings(env, repoFullName); await reReviewStoredPullRequest(