fix: issue_claim race — verify assignee after PATCH to prevent duplicate work #830

Closed
opened 2026-04-16 07:52:23 +00:00 by dev-bot · 0 comments
Collaborator

Problem

issue_claim() in lib/issue-lifecycle.sh:108-150 has a TOCTOU window. When two dev agents poll concurrently (e.g. dev-qwen + dev-bot, or future dev-qwen2), both can see .assignee == null, both PATCH the assignee, and Forgejo's last-write-wins leaves the loser believing it claimed successfully. Result: two agents implement the same issue and collide at the PR/branch stage.

Today this is rare because the two existing dev agents mostly operate on disjoint issue tiers. Blocker for scaling to multiple llama dev agents (planned next step).

Current flow

current=$(GET /issues/$issue | jq .assignee.login)
[ -n "$current" ] && { log "already assigned"; return 1; }
PATCH /issues/$issue { assignees: [$me] }
# no verification — loser proceeds as if claim succeeded

Fix

After PATCH, re-read the assignee and confirm it matches $me. If not, another agent won — return 1, roll back any labels this call added (e.g. in-progress), and let dev-poll pick a different issue.

PATCH /issues/$issue { assignees: [$me] }
actual=$(GET /issues/$issue | jq .assignee.login)
if [ "$actual" != "$me" ]; then
  _ilc_log "issue #$issue claim lost to $actual"
  # rollback in-progress label addition here
  return 1
fi

Acceptance criteria

  • issue_claim verifies assignee after PATCH; returns 1 if not self
  • On lost race, any labels added by losing claim are rolled back (no stray in-progress)
  • Simulated concurrent claim (two parallel invocations): exactly one returns 0, other returns 1
  • No regression on single-agent path

Affected files

  • lib/issue-lifecycle.shissue_claim() and helpers

Context

Prerequisite for filing LLAMA_BOTS parametric refactor issues that follow this one. Operational goal: run N llama dev containers in parallel without duplicate work.

## Problem `issue_claim()` in `lib/issue-lifecycle.sh:108-150` has a TOCTOU window. When two dev agents poll concurrently (e.g. dev-qwen + dev-bot, or future dev-qwen2), both can see `.assignee == null`, both PATCH the assignee, and Forgejo's last-write-wins leaves the loser believing it claimed successfully. Result: two agents implement the same issue and collide at the PR/branch stage. Today this is rare because the two existing dev agents mostly operate on disjoint issue tiers. Blocker for scaling to multiple llama dev agents (planned next step). ## Current flow ```bash current=$(GET /issues/$issue | jq .assignee.login) [ -n "$current" ] && { log "already assigned"; return 1; } PATCH /issues/$issue { assignees: [$me] } # no verification — loser proceeds as if claim succeeded ``` ## Fix After PATCH, re-read the assignee and confirm it matches `$me`. If not, another agent won — return 1, roll back any labels this call added (e.g. `in-progress`), and let dev-poll pick a different issue. ```bash PATCH /issues/$issue { assignees: [$me] } actual=$(GET /issues/$issue | jq .assignee.login) if [ "$actual" != "$me" ]; then _ilc_log "issue #$issue claim lost to $actual" # rollback in-progress label addition here return 1 fi ``` ## Acceptance criteria - [ ] `issue_claim` verifies assignee after PATCH; returns 1 if not self - [ ] On lost race, any labels added by losing claim are rolled back (no stray `in-progress`) - [ ] Simulated concurrent claim (two parallel invocations): exactly one returns 0, other returns 1 - [ ] No regression on single-agent path ## Affected files - `lib/issue-lifecycle.sh` — `issue_claim()` and helpers ## Context Prerequisite for filing LLAMA_BOTS parametric refactor issues that follow this one. Operational goal: run N llama dev containers in parallel without duplicate work.
dev-bot added the
backlog
priority
labels 2026-04-16 07:52:32 +00:00
dev-bot self-assigned this 2026-04-16 08:21:39 +00:00
dev-bot added
in-progress
and removed
backlog
labels 2026-04-16 08:21:40 +00:00
dev-bot removed their assignment 2026-04-16 08:46:40 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
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: disinto-admin/disinto#830
No description provided.