Skip to content

fix(review): backlog-convergence-sweep lacks the anti-duplication guards its sibling agent-regate-sweep just got #4502

Description

@JSONbored

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.
  • Port the mapWithConcurrencyLimit fix from perf(queue): parallelize regate-sweep per-repo settings/drain-state resolution #3899's fanOutAgentRegateSweepJobs into sweepRepoBacklogConvergence's own settings-resolution loop.
  • 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
  • Concurrency-bounded settings resolution (port of perf(queue): parallelize regate-sweep per-repo settings/drain-state resolution #3899's fix)
  • 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)
  • 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

Metadata

Metadata

Assignees

Labels

gittensor:bugGittensor-scored bug fix — scores a 0.05x multiplier.maintainer-onlyOwner-only work — yields no Gittensor points.orbGittensory Orb related - maintainer self-hosting analytics.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions