[object Object]

← back to Rentv 2026

pr/jobs: cap convergence-controller re-ticks (runaway guard). The pending>0 branch re-enqueued the controller every 3min with tick+1 but never checked a max — a single job wedged in queued/running would re-tick FOREVER (none of the round/budget caps are reached while waiting), reintroducing exactly the unbounded-loop anti-pattern a prior Cody gate flagged. Added max_ticks (default 15 ≈ 45min) → stop('pending_stuck'). Verified bounded on a stuck job, normal drain unaffected

7c58dbc9e6bfc3fece683d06dfab7a74c512f3e4 · 2026-08-05 23:22:01 -0700 · Steve

Files touched

Diff

commit 7c58dbc9e6bfc3fece683d06dfab7a74c512f3e4
Author: Steve <steve@designerwallcoverings.com>
Date:   Wed Aug 5 23:22:01 2026 -0700

    pr/jobs: cap convergence-controller re-ticks (runaway guard). The pending>0 branch re-enqueued the controller every 3min with tick+1 but never checked a max — a single job wedged in queued/running would re-tick FOREVER (none of the round/budget caps are reached while waiting), reintroducing exactly the unbounded-loop anti-pattern a prior Cody gate flagged. Added max_ticks (default 15 ≈ 45min) → stop('pending_stuck'). Verified bounded on a stuck job, normal drain unaffected
---
 src/pr/jobs/index.js | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/pr/jobs/index.js b/src/pr/jobs/index.js
index d1023e10..b805f9b6 100644
--- a/src/pr/jobs/index.js
+++ b/src/pr/jobs/index.js
@@ -506,6 +506,7 @@ const handlers = {
     const target = Number(p.target_pct) || 70;
     const round = Number(p.round) || 1;
     const maxRounds = Number(p.max_rounds) || 6;
+    const maxTicks = Number(p.max_ticks) || 15; // cap the "waiting for research to drain" re-ticks (~45 min) so a wedged job can't re-enqueue this controller forever
     const gate = await settings.californiaGate();
     const cov = gate.checks.contact_coverage;
     const sweepCreated = Number((await db.one(
@@ -525,8 +526,11 @@ const handlers = {
       return stateDoc;
     };
     if (pending > 0) { // research still draining — check again shortly, same round
+      // Bound the wait: a job wedged in queued/running would otherwise re-tick this controller
+      // every 3 min forever (none of the round/budget caps are reached while we're in this branch).
+      if ((p.tick || 0) >= maxTicks) return stop('pending_stuck');
       await enqueue('convergence-controller', { ...p, tick: (p.tick || 0) + 1 }, { priority: 7, run_after: new Date(Date.now() + 3 * 60e3).toISOString() });
-      return { waiting: pending, round, coverage_pct: cov.pct };
+      return { waiting: pending, round, tick: (p.tick || 0) + 1, coverage_pct: cov.pct };
     }
     if (cov.pass || cov.pct >= target) return stop('target_reached');
     if (round > 1 && sweepCreated === 0) return stop('sweep_dry');

← f1d77cee auto-save: 2026-08-05T23:16:37 (7 files) — data/deals-regist  ·  back to Rentv 2026  ·  pr/jobs: Cody gate on convergence-controller — (1) singleton af6ccef9 →