fix: use Forgejo assignee as issue lock to prevent concurrent claims #38

Closed
opened 2026-03-28 19:17:02 +00:00 by dev-bot · 0 comments
Collaborator

Problem

issue_claim() in lib/issue-lifecycle.sh adds the in-progress label but never checks if another agent already claimed the issue. With multiple dev-agent containers, both can claim the same issue simultaneously.

Additionally, dev-poll.sh has recovery logic that picks up in-progress issues without a PR ("orphaned"). When two agents run, agent B sees agent A's freshly-claimed issue as orphaned and starts working on it too.

Fix

Use Forgejo's issue assignee as an atomic lock.

issue_claim changes

In lib/issue-lifecycle.sh, issue_claim() should:

  1. Assign the issue to the current bot user ($FORGE_BOT_USER, derived from the token)
  2. Check the response — if already assigned to someone else, abort (return 1)
  3. Only then add in-progress label and remove backlog
issue_claim() {
  local issue="$1"

  # Get current bot identity
  local me
  me=$(curl -sf -H "Authorization: token ${FORGE_TOKEN}"     "${FORGE_URL}/api/v1/user" | jq -r '.login') || return 1

  # Check current assignee
  local current
  current=$(curl -sf -H "Authorization: token ${FORGE_TOKEN}"     "${FORGE_API}/issues/${issue}" | jq -r '.assignee.login // ""') || return 1

  if [ -n "$current" ] && [ "$current" != "$me" ]; then
    _ilc_log "issue #${issue} already assigned to ${current} — skipping"
    return 1
  fi

  # Assign to self (atomic — Forgejo rejects if already assigned differently? TBD: verify)
  curl -sf -X POST     -H "Authorization: token ${FORGE_TOKEN}"     -H "Content-Type: application/json"     "${FORGE_API}/issues/${issue}"     -d "{\"assignees\":[\"${me}\"]}" >/dev/null 2>&1 || return 1

  # Now add labels
  # ... existing label logic ...
}

dev-poll recovery changes

In dev-poll.sh, the orphaned-issue recovery (Priority 1) should check the assignee before adopting:

  • If assigned to this bot: recover (agent crashed, resume)
  • If assigned to another bot: skip (other agent is working on it)
  • If unassigned: treat as truly orphaned, claim normally

dev-agent cleanup

issue_release() and issue_close() should clear the assignee.

Affected files

  • lib/issue-lifecycle.shissue_claim(), issue_release(), issue_close()
  • dev/dev-poll.sh — orphan recovery section

Acceptance criteria

  • issue_claim() assigns to self, returns 1 if already assigned to another
  • dev-agent.sh checks return code, exits cleanly if claim fails
  • Recovery only adopts issues assigned to self or unassigned
  • issue_release() and issue_close() clear assignee
## Problem `issue_claim()` in `lib/issue-lifecycle.sh` adds the `in-progress` label but never checks if another agent already claimed the issue. With multiple dev-agent containers, both can claim the same issue simultaneously. Additionally, `dev-poll.sh` has recovery logic that picks up `in-progress` issues without a PR ("orphaned"). When two agents run, agent B sees agent A's freshly-claimed issue as orphaned and starts working on it too. ## Fix Use Forgejo's issue assignee as an atomic lock. ### issue_claim changes In `lib/issue-lifecycle.sh`, `issue_claim()` should: 1. Assign the issue to the current bot user (`$FORGE_BOT_USER`, derived from the token) 2. Check the response — if already assigned to someone else, abort (return 1) 3. Only then add `in-progress` label and remove `backlog` ```bash issue_claim() { local issue="$1" # Get current bot identity local me me=$(curl -sf -H "Authorization: token ${FORGE_TOKEN}" "${FORGE_URL}/api/v1/user" | jq -r '.login') || return 1 # Check current assignee local current current=$(curl -sf -H "Authorization: token ${FORGE_TOKEN}" "${FORGE_API}/issues/${issue}" | jq -r '.assignee.login // ""') || return 1 if [ -n "$current" ] && [ "$current" != "$me" ]; then _ilc_log "issue #${issue} already assigned to ${current} — skipping" return 1 fi # Assign to self (atomic — Forgejo rejects if already assigned differently? TBD: verify) curl -sf -X POST -H "Authorization: token ${FORGE_TOKEN}" -H "Content-Type: application/json" "${FORGE_API}/issues/${issue}" -d "{\"assignees\":[\"${me}\"]}" >/dev/null 2>&1 || return 1 # Now add labels # ... existing label logic ... } ``` ### dev-poll recovery changes In `dev-poll.sh`, the orphaned-issue recovery (Priority 1) should check the assignee before adopting: - If assigned to this bot: recover (agent crashed, resume) - If assigned to another bot: skip (other agent is working on it) - If unassigned: treat as truly orphaned, claim normally ### dev-agent cleanup `issue_release()` and `issue_close()` should clear the assignee. ## Affected files - `lib/issue-lifecycle.sh` — `issue_claim()`, `issue_release()`, `issue_close()` - `dev/dev-poll.sh` — orphan recovery section ## Acceptance criteria - [ ] `issue_claim()` assigns to self, returns 1 if already assigned to another - [ ] `dev-agent.sh` checks return code, exits cleanly if claim fails - [ ] Recovery only adopts issues assigned to self or unassigned - [ ] `issue_release()` and `issue_close()` clear assignee
dev-bot added the
in-progress
label 2026-03-28 19:17:02 +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: johba/disinto#38
No description provided.