fix: claim gate re-claims issues with open ready PRs, rebuilding + replacing their branches (destroys rework fixes) #496

Closed
opened 2026-08-09 20:52:48 +00:00 by igor · 0 comments
Collaborator

The issue grind re-claimed tickets whose PRs were already OPEN and READY, rebuilding each from scratch and pushing to the same agent/<n>-slug branch -- replacing the branch and destroying the rework rounds' fixes. Journal evidence (2026-08-09):

14:02:10 claiming joshtronic/igor#490   (PR #492 already open, post-rework head)
15:04:47 claiming joshtronic/igor#491   (PR #494 already open, post-rework head)
15:34:26 claiming joshtronic/igor#491   (again)
15:49:36 claiming joshtronic/igor#491   (again)

Observed damage: PR #492 lost its MD036 fix; PR #494 lost its round-2 fixes twice and cycled through three divergent implementations; every replacement re-triggered shadow review, burning review/rework cycles on regressions. The operator was initially blamed for hand-squashing; he was blogging.

Contributing context (reconstruct the exact hole from state, not this guess): both issues had been through the PR rework flow, including operator-side reassignment of the PR to the bot for a rework round and later unassignment of the ISSUE. The claim gate's open-PR dedup evidently keys on state that this sequence clears.

Deliverables

  1. Find the actual gap: trace the discovery/claim gate's "does this issue already have an open PR" logic against the recorded state of #490/#491 (branch exists at agent/<n>-*, ready non-WIP PR open, Closes #<n> in body). Identify which signal the rework/reassignment sequence cleared.
  2. Make the guard structural, not stateful: before claiming, if an OPEN pull request exists whose head branch matches agent/<issue>-* (or whose body carries the closing keyword for the issue), the issue is NOT claimable -- regardless of assignment history, review state, or anything in discretionary-state.json. A WIP (checkpoint) PR keeps the existing RESUME path; a ready PR means the issue's work is in review and the grind's job is done.
  3. Never push over an existing branch from a fresh claim: as defense in depth, if the claim path is about to create branch agent/<n>-<slug> and origin already has ANY agent/<n>-* branch, abort the claim loudly instead of replacing history.
  4. Tests: ready-PR-open -> not claimable; WIP-PR-open -> resume (unchanged); no-PR + stale branch on origin -> abort with the loud message; plain unclaimed issue -> claimable (unchanged).

Out of scope

  • Repairing PRs #492/#494 (handled operator-side).
  • The rework/reassignment flow itself.

Verification

  • make test green including the four new cases; the deliverable-1 trace written up in the PR body (which signal was cleared, by what).

OPERATOR DIRECTION (2026-08-09, appended before claim): the unassigned-while-worked convention is itself part of this bug's surface -- "the 'agent unassigned' dance is part of the issue." Deliverable 2 is AMENDED to make assignment the primary lock, with the open-PR check as structural backstop:

  • Claim = assign the issue to the bot, and it STAYS assigned through build, review, rework rounds, and re-review -- for the whole lifecycle of its PR. Assigned-to-bot = locked; the grind never claims an assigned issue.
  • Unassign happens in exactly two places: the issue closes (PR merged -- moot), or the work genuinely returns to the claimable pool (a discard with no PR, or an operator explicitly re-queueing).
  • Reconcile the recovery sweep: the historical reason for unassigned-while-worked is that the recovery sweep treated assigned-to-bot as "orphaned claim, re-queue it" and churned. The sweep must learn the difference: assigned + open PR (any state) = in-flight, hands off; assigned + no PR + no worktree activity beyond a staleness threshold = genuinely orphaned, recover. Get this boundary right or the lock recreates the churn it replaces.
  • The open-PR / existing-branch guards from the original deliverables 2-3 stay, as backstops for states assignment can't see.
The issue grind re-claimed tickets whose PRs were already OPEN and READY, rebuilding each from scratch and pushing to the same `agent/<n>-slug` branch -- replacing the branch and destroying the rework rounds' fixes. Journal evidence (2026-08-09): ``` 14:02:10 claiming joshtronic/igor#490 (PR #492 already open, post-rework head) 15:04:47 claiming joshtronic/igor#491 (PR #494 already open, post-rework head) 15:34:26 claiming joshtronic/igor#491 (again) 15:49:36 claiming joshtronic/igor#491 (again) ``` Observed damage: PR #492 lost its MD036 fix; PR #494 lost its round-2 fixes twice and cycled through three divergent implementations; every replacement re-triggered shadow review, burning review/rework cycles on regressions. The operator was initially blamed for hand-squashing; he was blogging. Contributing context (reconstruct the exact hole from state, not this guess): both issues had been through the PR rework flow, including operator-side reassignment of the PR to the bot for a rework round and later unassignment of the ISSUE. The claim gate's open-PR dedup evidently keys on state that this sequence clears. ## Deliverables 1. **Find the actual gap:** trace the discovery/claim gate's "does this issue already have an open PR" logic against the recorded state of #490/#491 (branch exists at `agent/<n>-*`, ready non-WIP PR open, `Closes #<n>` in body). Identify which signal the rework/reassignment sequence cleared. 2. **Make the guard structural, not stateful:** before claiming, if an OPEN pull request exists whose head branch matches `agent/<issue>-*` (or whose body carries the closing keyword for the issue), the issue is NOT claimable -- regardless of assignment history, review state, or anything in `discretionary-state.json`. A WIP (checkpoint) PR keeps the existing RESUME path; a ready PR means the issue's work is in review and the grind's job is done. 3. **Never push over an existing branch from a fresh claim:** as defense in depth, if the claim path is about to create branch `agent/<n>-<slug>` and origin already has ANY `agent/<n>-*` branch, abort the claim loudly instead of replacing history. 4. **Tests:** ready-PR-open -> not claimable; WIP-PR-open -> resume (unchanged); no-PR + stale branch on origin -> abort with the loud message; plain unclaimed issue -> claimable (unchanged). ## Out of scope - Repairing PRs #492/#494 (handled operator-side). - The rework/reassignment flow itself. ## Verification - `make test` green including the four new cases; the deliverable-1 trace written up in the PR body (which signal was cleared, by what). --- **OPERATOR DIRECTION (2026-08-09, appended before claim):** the unassigned-while-worked convention is itself part of this bug's surface -- "the 'agent unassigned' dance is part of the issue." Deliverable 2 is AMENDED to make **assignment the primary lock**, with the open-PR check as structural backstop: - **Claim = assign the issue to the bot, and it STAYS assigned** through build, review, rework rounds, and re-review -- for the whole lifecycle of its PR. Assigned-to-bot = locked; the grind never claims an assigned issue. - **Unassign happens in exactly two places:** the issue closes (PR merged -- moot), or the work genuinely returns to the claimable pool (a discard with no PR, or an operator explicitly re-queueing). - **Reconcile the recovery sweep:** the historical reason for unassigned-while-worked is that the recovery sweep treated assigned-to-bot as "orphaned claim, re-queue it" and churned. The sweep must learn the difference: assigned + open PR (any state) = in-flight, hands off; assigned + no PR + no worktree activity beyond a staleness threshold = genuinely orphaned, recover. Get this boundary right or the lock recreates the churn it replaces. - The open-PR / existing-branch guards from the original deliverables 2-3 stay, as backstops for states assignment can't see.
igor self-assigned this 2026-08-09 20:56:42 +00:00
igor added spent time 2026-08-09 21:14:20 +00:00
17 minutes 14 seconds
igor removed their assignment 2026-08-09 21:14:21 +00:00
igor self-assigned this 2026-08-09 22:10:16 +00:00
igor added spent time 2026-08-09 22:21:46 +00:00
11 minutes 7 seconds
igor removed their assignment 2026-08-09 22:21:46 +00:00
igor closed this issue 2026-08-09 22:27:02 +00:00
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Total time spent: 28 minutes 21 seconds
igor
28 minutes 21 seconds
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
joshtronic/igor#496
No description provided.