fix(agents): add two-phase claim protocol to prevent duplicate PR reviews #1326
@@ -196,17 +196,37 @@ LOOP FOREVER:
|
||||
# Take up to N work items
|
||||
batch = work_items[:N]
|
||||
|
||||
# Claim all PRs in the batch BEFORE dispatching (distributed lock)
|
||||
# Claim all PRs in the batch using two-phase locking protocol
|
||||
claimed_items = []
|
||||
for item in batch:
|
||||
if item.type in ("review", "re_review"):
|
||||
token = f"{INSTANCE_ID}-{item.pr.number}-{timestamp}"
|
||||
post comment on PR:
|
||||
"Review claimed by reviewer pool instance <INSTANCE_ID>.
|
||||
Dispatching independent code review.
|
||||
"🔒 Review claimed by <INSTANCE_ID> [claim-token: <token>]
|
||||
|
||||
---
|
||||
**Automated by CleverAgents Bot**
|
||||
Supervisor: PR Review | Agent: ca-continuous-pr-reviewer"
|
||||
|
||||
# Phase 2: Wait 5 seconds, then verify claims
|
||||
wait 5 seconds
|
||||
for item in batch:
|
||||
if item.type in ("review", "re_review"):
|
||||
comments = re-fetch PR comments
|
||||
competing_claims = [c for c in comments if "claim-token:" in c.body
|
||||
and c.user == our_user
|
||||
and c.body does not contain our token]
|
||||
if competing_claims exist:
|
||||
their_token = extract claim-token from competing_claims[0]
|
||||
our_token = f"{INSTANCE_ID}-{item.pr.number}-{timestamp}"
|
||||
if our_token > their_token:
|
||||
# We lose — delete our claim and skip
|
||||
delete our claim comment
|
||||
continue
|
||||
claimed_items.append(item)
|
||||
|
||||
batch = claimed_items # Only dispatch for PRs we successfully claimed
|
||||
|
||||
# Dispatch N parallel ca-pr-self-reviewer sessions (fire-and-forget)
|
||||
active_reviews = {} # pr_number -> session_id
|
||||
for item in batch:
|
||||
@@ -313,12 +333,20 @@ Multiple reviewer pools or standalone reviewers may run simultaneously. To
|
||||
prevent duplicate reviews:
|
||||
|
||||
1. **Claim via comment.** Before dispatching a reviewer for a PR, post a
|
||||
comment: `"Review claimed by reviewer pool instance <INSTANCE_ID>."`
|
||||
comment: `"🔒 Review claimed by <INSTANCE_ID> [claim-token: <UNIQUE_TOKEN>]"`
|
||||
where `<UNIQUE_TOKEN>` is a unique value like `<INSTANCE_ID>-<PR_NUMBER>-<TIMESTAMP>`.
|
||||
2. **Check before claiming.** Always fetch fresh comments and check for
|
||||
existing claims before posting your own.
|
||||
3. **Race condition tolerance.** If a dispatched reviewer finds the PR was
|
||||
3. **Verify after claiming (double-check protocol).** After posting your
|
||||
claim comment, wait 5 seconds, then re-fetch the PR comments. If another
|
||||
claim comment appeared from a different instance within the same window,
|
||||
the instance with the **lexicographically smaller claim-token wins**. The
|
||||
losing instance must delete its claim comment and skip that PR. This
|
||||
two-phase protocol eliminates the race condition where two pools claim
|
||||
the same PR within seconds of each other.
|
||||
4. **Race condition tolerance.** If a dispatched reviewer finds the PR was
|
||||
already reviewed by someone else, it should yield gracefully.
|
||||
4. **Stale claims.** If a claim comment is older than 30 minutes with no
|
||||
5. **Stale claims.** If a claim comment is older than 30 minutes with no
|
||||
subsequent review activity, consider the claim stale and proceed with
|
||||
a new claim.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user