Public: anyone can read this; the crew can change it.

The merge queue remembers clashes: two pulls that are green alone but red together are never batched together again until one lands #4286

Merged opened this in answer to

Check · Review · Land · Live done — Landed and live in production.

Merged

Once the merge queue finds a pull red, that pull no longer rides batches with fresh pulls; it comes back only after a push or the 30-minute re-gate. The issue's own test can't pass as written. The tree from landing A, C and then B is the same tree as the red A+B+C candidate, so B can't land after A unless the gate is non-deterministic. The existing tests also rely on this: they hand B back without…

shipped production is serving it deploy 3a0f5c8

opened this

Once the merge queue finds a pull red, that pull no longer rides batches with fresh pulls; it comes back only after a push or the 30-minute re-gate. The issue's own test can't pass as written. The tree from landing A, C and then B is the same tree as the red A+B+C candidate, so B can't land after A unless the gate is non-deterministic. The existing tests also rely on this: they hand B back without a gate of its own.

Why pulls kept riding red batches. The search hands B back red on main+A. Under the old admission rule, a new main was a new try, so B joined the next batch after every landing, went red again, and cut off the speculative batch stacked on it.

What changed

  • A red stays out until a push (src/crew/autonomy.ts). A pull told red on its current head stays out of the queue when main moves. The 30-minute re-gate on the same head is kept.
  • The queue remembers its reds (src/checks/queue.ts). Each red is kept with its head and the pulls of its candidate that landed first and were in its red tree (none means red on its own merge).

- When a remembered pull joins again at the same head, it is gated in a candidate of its own. - That candidate is formed only once no fresh pull is waiting, and nothing is stacked on it, so a red re-check cuts off no fresh batch and A and B are never in one candidate again. - The memory is cleared when the pull lands or its head changes. - The log says red on its own merge or red on top of its candidate with=#….

  • The comment names the pulls: "Red on top of #A, which landed first…". It says both readings, because telling a real clash from B being red alone would cost one more gate. Item 2 ("the second waits for the first to land, then is re-checked on the new main") is met by the gate that already judged exactly that tree.
  • No-drip re-batching (item 3): unchanged from #4058, which already re-batches the good pulls together; the existing test still holds.
  • Tick line: clashes=N.
  • /boards: a new "Red batches, last hour" column (1 of 3). It counts first candidates only, not search prefixes or re-checks. It is added through a { batches: true } option, so the terminal and MCP rows keep their exact shape.

Tests

  • New test/merge-queue-clash.test.ts (gate red when the tree holds both a.txt and b.txt):

- A+B clash, C fine: A and C land, and B is handed back once "on top of" A. - B re-joining at the same head gates alone after the fresh D+E, and no fresh pull is stacked on it. - After a push, B is batched with the rest and lands.

  • test/merge-queue.test.ts gains one end-to-end case through the sweep: a red stays out when main moves, the re-gate checks it alone after the fresh pull, and /boards shows 1 of 3.
  • No existing test was changed. The type check is clean, 184 queue and cross-file tests pass, and 327 boards/telemetry/MCP tests pass. Design lint is clean.

Not done

  • No extra gate to prove a clash (B alone on the base before A).
  • Clashes are kept in memory only, like the rest of the line, so a restart forgets them. The red decision in the ledger still keeps the pull out after a restart.

To try it: bun test test/merge-queue-clash.test.ts test/merge-queue.test.ts, then on the preview watch the tick line for clashes= and the Merge queue card on /boards.

🤖 Generated with Claude Code

Closes #4235

commented

Once the merge queue finds a pull red, that pull no longer rides batches with fresh pulls; it comes back only after a push or the 30-minute re-gate. The issue's own test can't pass as written. The tree from landing A, C and then B is the same tree as the red A+B+C candidate, so B can't land after A unless the gate is non-deterministic. The existing tests also rely on this: they hand B back without a gate of its own.

Why pulls kept riding red batches. The search hands B back red on main+A. Under the old admission rule, a new main was a new try, so B joined the next batch after every landing, went red again, and cut off the speculative batch stacked on it.

What changed

  • A red stays out until a push (src/crew/autonomy.ts). A pull told red on its current head stays out of the queue when main moves. The 30-minute re-gate on the same head is kept.
  • The queue remembers its reds (src/checks/queue.ts). Each red is kept with its head and the pulls of its candidate that landed first and were in its red tree (none means red on its own merge).

- When a remembered pull joins again at the same head, it is gated in a candidate of its own. - That candidate is formed only once no fresh pull is waiting, and nothing is stacked on it, so a red re-check cuts off no fresh batch and A and B are never in one candidate again. - The memory is cleared when the pull lands or its head changes. - The log says red on its own merge or red on top of its candidate with=#….

  • The comment names the pulls: "Red on top of #A, which landed first…". It says both readings, because telling a real clash from B being red alone would cost one more gate. Item 2 ("the second waits for the first to land, then is re-checked on the new main") is met by the gate that already judged exactly that tree.
  • No-drip re-batching (item 3): unchanged from #4058, which already re-batches the good pulls together; the existing test still holds.
  • Tick line: clashes=N.
  • /boards: a new "Red batches, last hour" column (1 of 3). It counts first candidates only, not search prefixes or re-checks. It is added through a { batches: true } option, so the terminal and MCP rows keep their exact shape.

Tests

  • New test/merge-queue-clash.test.ts (gate red when the tree holds both a.txt and b.txt):

- A+B clash, C fine: A and C land, and B is handed back once "on top of" A. - B re-joining at the same head gates alone after the fresh D+E, and no fresh pull is stacked on it. - After a push, B is batched with the rest and lands.

  • test/merge-queue.test.ts gains one end-to-end case through the sweep: a red stays out when main moves, the re-gate checks it alone after the fresh pull, and /boards shows 1 of 3.
  • No existing test was changed. The type check is clean, 184 queue and cross-file tests pass, and 327 boards/telemetry/MCP tests pass. Design lint is clean.

Not done

  • No extra gate to prove a clash (B alone on the base before A).
  • Clashes are kept in memory only, like the rest of the line, so a restart forgets them. The red decision in the ledger still keeps the pull out after a restart.

To try it: bun test test/merge-queue-clash.test.ts test/merge-queue.test.ts, then on the preview watch the tick line for clashes= and the Merge queue card on /boards.

🤖 Generated with Claude Code

Changes requested — before the latest push

The queue logic is sound and well tested, and the preview answers 200. One miss against the list: the new red-batch figure is on the page only. queuesFor(app, reader, now, { batches: true }) is passed solely from flow-panel.ts, so the terminals Flow command (src/tui/forge.ts:290) and the MCP flow tool never show 1 of 3`. The pull says this was deliberate, to keep their row shape. That fails the both-doors rule: what the page shows, the terminal must show. Fix first: show the red-batch rate in the terminal Flow view, with a test, and say so in the README. The README currently says the three places show the same queue numbers, and the new paragraph names the flow panel only. If you keep it out of MCP, state that. Smaller point: the issue asked that two clashing pulls never share a candidate again. The pull instead keeps any red pull out of fresh batches. I think that is acceptable, but the pull description should say it chose this over a per-pair memory.

commented

Once the merge queue finds a pull red, that pull no longer rides batches with fresh pulls; it comes back only after a push or the 30-minute re-gate. After review, the red-batch rate now shows in the terminal's Flow command as well as on /boards.

Why pulls kept riding red batches. The search found B red on main+A and handed it back. Under the old rule a new main was a new try, so B joined the next batch after every landing, went red again, and cut off the speculative batch gated on top of it.

What changed

  • A red stays out until a push (src/crew/autonomy.ts). Moving main no longer lets it back in. The existing 30-minute re-gate on the same head stays.
  • The queue remembers each red it hands back (src/checks/queue.ts). It keeps the pull's head and the pulls of its candidate that landed first and were in its red tree. None of those means it was red on its own merge; some means a clash.

- When a remembered pull joins again at the same head, it is gated alone, only once no fresh pull is waiting, and nothing is stacked on it. A red re-check therefore cuts off no fresh batch. - A push or a landing clears it.

  • The red comment names those pulls: "Red on top of #A, which landed first…".
  • Tick line: clashes=N.
  • Red-batch rate for the last hour, as 1 of 3, worded by one shared function (redBatchWords in src/flow/queue.ts). It counts only each batch's first gate, not search prefixes or re-checks.

- On /boards it is a new column. - In the terminal's Flow command it is a Red batches, last hour: line under the queue table. The table row is already 58 columns wide and its columns are compared exactly by existing tests, so it is a line rather than a column.

  • Not in the MCP flow tool. Its queue rows keep their exact shape, which an existing test pins, and a test checks they have no batches field. The README says this.

Chose this over a per-pair memory. The issue asked that two clashing pulls never share a candidate again. This pull keeps any remembered red pull out of fresh batches instead. Without one extra gate, the queue can't tell "B clashes with A" from "B is red on main". By the time B is found red, A has landed, so a pair rule would only stop B riding with A, which can no longer happen.

The issue's test can't pass as written. After A and C land, B on the new main is the same tree as the red A+B+C candidate, so B can't land unless the gate is non-deterministic. The existing tests also rely on this: they hand B back without a gate of its own. So B is handed back once, and its later re-check is gated alone.

Tests. No existing test was changed.

  • test/merge-queue-clash.test.ts drives the queue directly: A and C land, and B is handed back once "on top of" A. Then B is re-checked alone after the fresh pulls, nothing is stacked on it, and a push lets it land.
  • test/merge-queue.test.ts gains one case through the sweep: the red stays out when main moves, the re-gate checks it alone, /boards and the terminal both show 1 of 3, and the MCP row has no batches field.
  • The type check and design lint are clean. The merge-queue, terminal, MCP, boards and cross-file suites pass (247 tests). The tests that read the terminal's Flow lines are in that run.

To try it: bun test test/merge-queue-clash.test.ts test/merge-queue.test.ts. On the preview, watch for clashes= on the tick line, the Merge queue card on /boards, and the terminal's Flow command.

🤖 Generated with Claude Code

Approved

Approved. A red pull now stays out until a push (or the 30-minute re-gate on the same head), is re-checked alone after fresh pulls, and nothing stacks on it. That serves the stability and fast-landing priorities. Tests cover the queue directly and through the sweep, and the preview answers 200. The Red batches column on /boards sits behind the queue card, so I could not see it signed out. It reuses the existing table, mono cells and data attributes, with no new component or JS. The terminal gets the same figure as a line, and the MCP rows keep their shape.

Two things I would do next:

  1. Bound the new memory. line.batches is only pruned when redBatchRate is read, and line.reds is only cleared on landing or a changed head. A closed pull or an unwatched /boards leaves entries behind. Prune batches on push and drop reds entries for closed pulls.
  2. Show the clash on the page. The comment names the pulls, but /boards only shows a rate. Surface queueClashes there too, as the tick line does. Also check the wider table at 360px: the new column makes it wider, so confirm it scrolls inside its wrapper.
commented

Once the merge queue finds a pull red, that pull no longer rides batches with fresh pulls; it comes back only after a push or the 30-minute re-gate. The red-batch rate shows on /boards and in the terminal's Flow command.

The latest message on #4235 is an automatic triage note: the issue has no pillar, so it waits behind the Later gate. Naming a pillar is a maintainer's call, not something to change in code, so nothing was changed or pushed for it.

Why pulls kept riding red batches. The search found B red on main+A and handed it back. Under the old rule a new main was a new try, so B joined the next batch after every landing, went red again, and cut off the speculative batch gated on top of it.

What changed

  • A red stays out until a push (src/crew/autonomy.ts). Moving main no longer lets it back in. The existing 30-minute re-gate on the same head stays.
  • The queue remembers each red it hands back (src/checks/queue.ts). It keeps the pull's head and the pulls of its candidate that landed first and were in its red tree. None of those means it was red on its own merge; some means a clash.

- When a remembered pull joins again at the same head, it is gated alone, only once no fresh pull is waiting, and nothing is stacked on it. A red re-check therefore cuts off no fresh batch. - A push or a landing clears it.

  • The red comment names those pulls: "Red on top of #A, which landed first…".
  • Tick line: clashes=N.
  • Red-batch rate for the last hour, as 1 of 3, worded by one shared function (redBatchWords in src/flow/queue.ts). It counts only each batch's first gate, not search prefixes or re-checks.

- On /boards it is a new column. - In the terminal's Flow command it is a Red batches, last hour: line under the queue table. The table row is already 58 columns wide and its columns are compared exactly by existing tests, so it is a line rather than a column.

  • Not in the MCP flow tool. Its queue rows keep their exact shape, which an existing test pins, and a test checks they have no batches field. The README says this.

Chose this over a per-pair memory. The issue asked that two clashing pulls never share a candidate again. This pull keeps any remembered red pull out of fresh batches instead. Without one extra gate, the queue can't tell "B clashes with A" from "B is red on main". By the time B is found red, A has landed, so a pair rule would only stop B riding with A, which can no longer happen.

The issue's test can't pass as written. After A and C land, B on the new main is the same tree as the red A+B+C candidate, so B can't land unless the gate is non-deterministic. The existing tests also rely on this: they hand B back without a gate of its own. So B is handed back once, and its later re-check is gated alone.

Tests. No existing test was changed.

  • test/merge-queue-clash.test.ts drives the queue directly: A and C land, and B is handed back once "on top of" A. Then B is re-checked alone after the fresh pulls, nothing is stacked on it, and a push lets it land.
  • test/merge-queue.test.ts gains one case through the sweep: the red stays out when main moves, the re-gate checks it alone, /boards and the terminal both show 1 of 3, and the MCP row has no batches field.
  • The type check and design lint are clean. The merge-queue, terminal, MCP, boards and cross-file suites pass (247 tests). The tests that read the terminal's Flow lines are in that run.

To try it: bun test test/merge-queue-clash.test.ts test/merge-queue.test.ts. On the preview, watch for clashes= on the tick line, the Merge queue card on /boards, and the terminal's Flow command.

🤖 Generated with Claude Code

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs