fix: bug: dev-poll stale detection races with issue_claim — blocks freshly claimed issues (#471)
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful

Add 60-second grace period to stale in-progress detection in dev-poll.sh.
When a poller sees an in-progress issue with no assignee/PR/lock, it now
checks the timeline API for when the label was added. If <60s ago, it
skips stale detection to allow the claiming agent time to finish its
assign+label sequence.

Also documents the intentional assign-before-label ordering in
issue_claim() that minimizes the race window.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude 2026-04-09 14:45:30 +00:00
parent 9d0b7f2b07
commit 8679332756
2 changed files with 43 additions and 4 deletions

View file

@ -103,6 +103,37 @@ is_blocked() {
# STALENESS DETECTION FOR IN-PROGRESS ISSUES
# =============================================================================
# Check if in-progress label was added recently (within grace period).
# Prevents race where a poller marks an issue as stale before the claiming
# agent's assign + label sequence has fully propagated. See issue #471.
# Args: issue_number [grace_seconds]
# Returns: 0 if recently added (within grace period), 1 if not
in_progress_recently_added() {
local issue="$1" grace="${2:-60}"
local now label_ts delta
now=$(date +%s)
# Query issue timeline for the most recent in-progress label event
label_ts=$(curl -sf -H "Authorization: token ${FORGE_TOKEN}" \
"${API}/issues/${issue}/timeline" | \
jq -r '[.[] | select(.type == "label") | select(.label.name == "in-progress")] | last | .created_at // empty') || true
if [ -z "$label_ts" ]; then
return 1 # no label event found — not recently added
fi
# Convert ISO timestamp to epoch and compare
local label_epoch
label_epoch=$(date -d "$label_ts" +%s 2>/dev/null || echo 0)
delta=$(( now - label_epoch ))
if [ "$delta" -lt "$grace" ]; then
return 0 # within grace period
fi
return 1
}
# Check if there's an open PR for a specific issue
# Args: issue_number
# Returns: 0 if open PR exists, 1 if not
@ -460,9 +491,15 @@ if [ "$ORPHAN_COUNT" -gt 0 ]; then
fi
if [ "$OPEN_PR" = false ] && [ "$BLOCKED_BY_INPROGRESS" = false ]; then
log "issue #${ISSUE_NUM} is stale (no assignee, no open PR, no agent lock) — relabeling to blocked"
relabel_stale_issue "$ISSUE_NUM" "no_assignee_no_open_pr_no_lock"
BLOCKED_BY_INPROGRESS=true
# Grace period: skip if in-progress label was added <60s ago (issue #471)
if in_progress_recently_added "$ISSUE_NUM" 60; then
log "issue #${ISSUE_NUM} in-progress label added <60s ago — skipping stale detection (grace period)"
BLOCKED_BY_INPROGRESS=true
else
log "issue #${ISSUE_NUM} is stale (no assignee, no open PR, no agent lock) — relabeling to blocked"
relabel_stale_issue "$ISSUE_NUM" "no_assignee_no_open_pr_no_lock"
BLOCKED_BY_INPROGRESS=true
fi
fi
# Formula guard: formula-labeled issues should not be worked on by dev-agent.

View file

@ -102,7 +102,9 @@ issue_claim() {
return 1
fi
# Assign to self (Forgejo rejects if already assigned differently)
# Assign to self BEFORE adding in-progress label (issue #471).
# This ordering ensures the assignee is set by the time other pollers
# see the in-progress label, reducing the stale-detection race window.
curl -sf -X PATCH \
-H "Authorization: token ${FORGE_TOKEN}" \
-H "Content-Type: application/json" \