Skip to content

ReviewGate: a previous round's pass timer escalates the current round, because attempt resets to 0 each round and timers are never cancelled #1963

Description

@ryanrborn

Observed 2026-09-22 on vs-7r3soq (vstim, review_gate.timeout_ms = 3,600,000). Round-2 revising passes "timed out" after about 19 and 27 minutes. The same workspace's vs-cpsreu round-1 pass timed out at exactly 60:00, so the config itself is honored.

Mechanism (coordinator-verified on origin/main):

  • launch_worker/5 arms Process.send_after(self(), {:timeout, attempt}, timeout_ms) at apps/arbiter/lib/arbiter/worker/review_gate.ex:3815. The timer ref isn't kept, and the file has no Process.cancel_timer.
  • The handler handle_info({:timeout, attempt}, %{attempt: attempt} = state) at :1261 escalates any timeout whose attempt equals the current one. It relies on attempt numbers being unique.
  • dispatch_next_review/2 resets attempt: 0 for every new round (:1915, from bd-bgeo6i, so reprompt budgets start fresh). Each round therefore numbers its passes the same way: reviewer = 1, implementer = 2.
  • So round 1's implementer timer {:timeout, 2} fires 60 minutes after round 1's implementer launched, lands while round 2's implementer is running as attempt 2, and is accepted.

The journal matches to the second (EDT):

impl1 launched impl2 launched Timeout fired
02:54:22 03:35:23 03:54:23 (impl1 + 60:01)
04:36:10 05:09:20 05:36:10 (impl1 + 60:00)

The same collision can hit a round-2+ reviewer (attempt 1) through round 1's reviewer timer. vs-7r3soq's earlier "reviewing pass timed out" parks may be this too; they weren't correlated.

Cost: long vstim reviews (a roughly 4½-minute test suite plus credo, twice per round) repeatedly lost a live revising pass mid-edit. That forced coordinator resumes and discarded work in progress.

Fix: make timer identity unique for the gate's lifetime (for example {round, attempt} or a make_ref() token in the message), or keep the timer ref and Process.cancel_timer/1 it when a pass ends or a new one launches. Keep bd-bgeo6i's per-round reprompt-budget reset.

D2: one module and one invariant, but it's the gate's liveness logic, so it needs careful tests.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions