You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Part of #4496. P6 — medium severity, medium confidence.
Context
agent-regate-sweep now has three layers of protection against a second fan-out piling duplicate work onto an unfinished one: (1) src/index.ts's REGATE_SWEEP_TRIGGER_TYPES backlog check (index.ts:30, 137-150) skips re-arming the top-level trigger while one is pending/processing; (2) fanOutAgentRegateSweepJobs's atomic claimRegateFanoutSlot (processors.ts:1339, 90s window) collapses a burst of fan-out jobs; (3) isRegateSweepDraining (processors.ts:1392, agent-sweep.ts:189-199) skips any repo whose own sweep is still draining.
backlog-convergence-sweep — same shape, fired unconditionally every 30 minutes (index.ts:178, not even gated by the sweepThrottledUntil rate-limit flag its neighbor gets) — has NONE of the three: it's absent from REGATE_SWEEP_TRIGGER_TYPES; fanOutBacklogConvergenceSweepJobs (processors.ts:2029-2070) calls no fan-out-slot claim; sweepRepoBacklogConvergence (processors.ts:2133-2193) has no per-repo draining check. Its only "already handled" signal is lastPublishedSurfaceSha matching the live head, which by design is stamped ONLY on a genuinely completed publish — never optimistically at dispatch time — so while a prior cycle's fanned-out agent-regate-pr jobs are still mid-flight, the candidate is still selected and duplicate jobs get re-enqueued on the next tick.
The resolution loop (sweepRepoBacklogConvergence) is also still a plain sequential for...await resolveRepositorySettings with no concurrency bound — the exact scaling problem #3899 already fixed for its sibling fanOutAgentRegateSweepJobs (which now resolves repo settings concurrently via mapWithConcurrencyLimit, processors.ts:1366-1393).
A concrete trigger for the duplication: queueProcessingTimeoutMs() defaults to 30 minutes (queue-common.ts:23, 694-699), and a crashed/restarted worker leaves its claimed row status: "processing" until that reclaim timeout — which coincides almost exactly with the 30-minute cron cadence that re-arms backlog-convergence-sweep. Since it has no pending/processing backlog check (unlike its sibling), a stuck-processing row from one cycle doesn't stop the next cycle's trigger from firing and re-enqueuing duplicate per-repo (and potentially per-PR agent-regate-pr) jobs. jobCoalesceKey does give it a job_key (queue-common.ts:896-899), but the enqueue-time coalesce lookup is scoped to status = 'pending' only (pg-queue.ts:911-917) — a trigger that's already processing is invisible to it, so a fresh duplicate row still gets inserted.
Requirements
Add backlog-convergence-sweep to a queue-backlog check mirroring REGATE_SWEEP_TRIGGER_TYPES, so a second trigger doesn't re-arm while one is pending/processing.
Give fanOutBacklogConvergenceSweepJobs its own claimRegateFanoutSlot-style atomic dedup (or reuse the same mechanism with a distinct key).
Give sweepRepoBacklogConvergence a per-repo draining guard, e.g. based on the newest agent-regate-pr dispatch for a backlog-convergence:-prefixed deliveryId within the sweep window.
Invariant + regression tests (non-negotiable): an invariant test asserting a second backlog-convergence-sweep trigger while a prior one is still pending/processing is deferred, not duplicated (mirroring the equivalent existing test for agent-regate-sweep); a regression test simulating the worker-crash/reclaim-timeout scenario (a stuck processing trigger row, then a new cron tick), asserting no duplicate per-repo/per-PR jobs are enqueued; a test confirming the concurrency-bounded settings resolution still resolves every candidate repo correctly (no repo silently dropped).
Deliverables
Backlog check for backlog-convergence-sweep mirroring REGATE_SWEEP_TRIGGER_TYPES
Atomic fan-out-slot claim for fanOutBacklogConvergenceSweepJobs
Per-repo draining guard for sweepRepoBacklogConvergence
Invariant test: duplicate trigger deferred, not duplicated
Regression test: worker-crash/reclaim-timeout scenario doesn't duplicate work
Expected outcome
backlog-convergence-sweep gets the same three-layer anti-duplication protection its sibling sweep already has, closing the narrow-but-real worker-crash/restart-timing gap and the settings-resolution scaling gap in the same pass.
References
src/index.ts:30, 137-150, 178 (the sibling's guard + this feature's unguarded trigger)
Part of #4496. P6 — medium severity, medium confidence.
Context
agent-regate-sweepnow has three layers of protection against a second fan-out piling duplicate work onto an unfinished one: (1)src/index.ts'sREGATE_SWEEP_TRIGGER_TYPESbacklog check (index.ts:30, 137-150) skips re-arming the top-level trigger while one is pending/processing; (2)fanOutAgentRegateSweepJobs's atomicclaimRegateFanoutSlot(processors.ts:1339, 90s window) collapses a burst of fan-out jobs; (3)isRegateSweepDraining(processors.ts:1392,agent-sweep.ts:189-199) skips any repo whose own sweep is still draining.backlog-convergence-sweep— same shape, fired unconditionally every 30 minutes (index.ts:178, not even gated by thesweepThrottledUntilrate-limit flag its neighbor gets) — has NONE of the three: it's absent fromREGATE_SWEEP_TRIGGER_TYPES;fanOutBacklogConvergenceSweepJobs(processors.ts:2029-2070) calls no fan-out-slot claim;sweepRepoBacklogConvergence(processors.ts:2133-2193) has no per-repo draining check. Its only "already handled" signal islastPublishedSurfaceShamatching the live head, which by design is stamped ONLY on a genuinely completed publish — never optimistically at dispatch time — so while a prior cycle's fanned-outagent-regate-prjobs are still mid-flight, the candidate is still selected and duplicate jobs get re-enqueued on the next tick.The resolution loop (
sweepRepoBacklogConvergence) is also still a plain sequentialfor...await resolveRepositorySettingswith no concurrency bound — the exact scaling problem #3899 already fixed for its siblingfanOutAgentRegateSweepJobs(which now resolves repo settings concurrently viamapWithConcurrencyLimit,processors.ts:1366-1393).A concrete trigger for the duplication:
queueProcessingTimeoutMs()defaults to 30 minutes (queue-common.ts:23, 694-699), and a crashed/restarted worker leaves its claimed rowstatus: "processing"until that reclaim timeout — which coincides almost exactly with the 30-minute cron cadence that re-armsbacklog-convergence-sweep. Since it has no pending/processing backlog check (unlike its sibling), a stuck-processing row from one cycle doesn't stop the next cycle's trigger from firing and re-enqueuing duplicate per-repo (and potentially per-PRagent-regate-pr) jobs.jobCoalesceKeydoes give it a job_key (queue-common.ts:896-899), but the enqueue-time coalesce lookup is scoped tostatus = 'pending'only (pg-queue.ts:911-917) — a trigger that's alreadyprocessingis invisible to it, so a fresh duplicate row still gets inserted.Requirements
backlog-convergence-sweepto a queue-backlog check mirroringREGATE_SWEEP_TRIGGER_TYPES, so a second trigger doesn't re-arm while one is pending/processing.fanOutBacklogConvergenceSweepJobsits ownclaimRegateFanoutSlot-style atomic dedup (or reuse the same mechanism with a distinct key).sweepRepoBacklogConvergencea per-repo draining guard, e.g. based on the newestagent-regate-prdispatch for abacklog-convergence:-prefixed deliveryId within the sweep window.mapWithConcurrencyLimitfix from perf(queue): parallelize regate-sweep per-repo settings/drain-state resolution #3899'sfanOutAgentRegateSweepJobsintosweepRepoBacklogConvergence's own settings-resolution loop.backlog-convergence-sweeptrigger while a prior one is still pending/processing is deferred, not duplicated (mirroring the equivalent existing test foragent-regate-sweep); a regression test simulating the worker-crash/reclaim-timeout scenario (a stuckprocessingtrigger row, then a new cron tick), asserting no duplicate per-repo/per-PR jobs are enqueued; a test confirming the concurrency-bounded settings resolution still resolves every candidate repo correctly (no repo silently dropped).Deliverables
backlog-convergence-sweepmirroringREGATE_SWEEP_TRIGGER_TYPESfanOutBacklogConvergenceSweepJobssweepRepoBacklogConvergenceExpected outcome
backlog-convergence-sweepgets the same three-layer anti-duplication protection its sibling sweep already has, closing the narrow-but-real worker-crash/restart-timing gap and the settings-resolution scaling gap in the same pass.References
src/index.ts:30, 137-150, 178(the sibling's guard + this feature's unguarded trigger)src/queue/processors.ts:1339, 1366-1393, 1392(the sibling's three guards + perf(queue): parallelize regate-sweep per-repo settings/drain-state resolution #3899's concurrency fix)src/queue/processors.ts:2029-2070, 2133-2193(this feature's fan-out + per-repo sweep, missing all of the above)src/selfhost/queue-common.ts:23, 694-699, 896-899(processing-timeout default + job_key)src/selfhost/pg-queue.ts:911-917, 939-942(the pending-only coalesce gap)Effort
M