Skip to content

feat(scripts): add review-bounty claim saturation checks (#797)#1188

Open
yanyishuai wants to merge 1 commit into
ramimbo:mainfrom
yanyishuai:fix/issue-797-review-bounty-claim-saturation
Open

feat(scripts): add review-bounty claim saturation checks (#797)#1188
yanyishuai wants to merge 1 commit into
ramimbo:mainfrom
yanyishuai:fix/issue-797-review-bounty-claim-saturation

Conversation

@yanyishuai

@yanyishuai yanyishuai commented Jun 30, 2026

Copy link
Copy Markdown

Summary

Add review-bounty claim saturation awareness to scripts/review_bounty_candidates.py for #797.

Changes

  • --bounty-issue live-mode flag loads active review-bounty issue comments via read-only gh issue view
  • Parse PR review URLs, PR comment URLs, bare PR references, and optional head/base SHAs from claim comments
  • New saturation states:
    • already_claimed_on_bounty_issue
    • already_claimed_current_head
    • claimed_by_pr_comment
    • claimed_stale_head_or_base
    • dirty_unclaimed_current_base_candidate
  • JSON/Markdown output includes matched claim URLs for auditability
  • Fixture input supports offline bounty_claim_comments + repo
  • Admin runbook documents the new flag

Verification

python -m pytest tests/test_review_bounty_candidates.py -q
python -m ruff check scripts/review_bounty_candidates.py tests/test_review_bounty_candidates.py

Fixes #797

Wallet: Do4v7foHJvRJLpRRoGaVPWX6DDEjX3yTK7J91gpwUQpE

Summary by CodeRabbit

  • New Features

    • Review reports now include bounty-claim matching, helping identify claimed, stale, and conflicting pull requests more clearly.
    • Live data can now incorporate claim information from related issue discussions for richer candidate analysis.
  • Bug Fixes

    • Improved handling of pull requests with outdated claim references and unclaimed dirty/conflicted states.
  • Documentation

    • Expanded admin runbook guidance for post-deploy checks and GitHub OAuth validation.

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@yanyishuai, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 25 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 8a81b98d-8229-49c2-841e-f014bcc9c037

📥 Commits

Reviewing files that changed from the base of the PR and between 4a54759 and 8792a8a.

📒 Files selected for processing (3)
  • docs/admin-runbook.md
  • scripts/review_bounty_candidates.py
  • tests/test_review_bounty_candidates.py
📝 Walkthrough

Walkthrough

scripts/review_bounty_candidates.py gains bounty-claim saturation: it parses claim evidence from bounty issue comments, builds a PR-to-claims index, and classifies duplicate/stale risk per PR. load_live_candidates and main are extended for --bounty-issue. Tests cover all new states. The admin runbook documents the new flag and adds OAuth deploy-gate steps.

Changes

Bounty Claim Saturation

Layer / File(s) Summary
Claim evidence parsing and index_bounty_claims
scripts/review_bounty_candidates.py
Replaces inline GH_TIMEOUT_SECONDS with an import, adds regex constants for claim evidence, and implements index_bounty_claims to parse PR review URLs, PR comment URLs, bare PR references, and optional head/base SHAs from bounty issue comments into a PR-keyed map.
PR classification with baseRefOid and saturation helpers
scripts/review_bounty_candidates.py
Adds baseRefOid to each PR row and introduces saturation helpers that match claims against a PR and adjust state/reason for evidence type, stale head/base SHA mismatches, and dirty/no-claim cases.
analyze_candidates integration and report formatting
scripts/review_bounty_candidates.py
analyze_candidates accepts repo, enables saturation when bounty_claim_comments are present, applies per-PR saturation during row construction, adds bounty_issue_claims_indexed to the summary, and extends text/markdown output with matched claim URLs.
Live collection and CLI
scripts/review_bounty_candidates.py
load_live_candidates accepts bounty_issue and fetches issue comments via gh; main adds --bounty-issue with mutual exclusivity against --input, positivity validation, and threads repo into analyze_candidates.
Tests
tests/test_review_bounty_candidates.py
Adds _pr fixture helper; analyze_candidates tests for all saturation states; index_bounty_claims bare-reference test; mocked live-mode integration test asserting bounty-claimed state; CLI validation test for --bounty-issue without --repo.
Admin runbook
docs/admin-runbook.md
Documents --bounty-issue behavior and all new state categories in the preflight example; adds GitHub OAuth section steps for check_public_mrwk_links.py and check_deploy_ready.py.

Possibly related issues

Possibly related PRs

  • ramimbo/mergework#743: Previously introduced review_bounty_candidates.py with load_live_candidates and analyze_candidates; this PR extends those exact entry points.
🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Bounty Pr Focus ⚠️ Warning Diff touches 178 files/46k lines, including .github/, app/, README, etc., so it’s far broader than the bounty script/tests/docs surfaces. Split unrelated repo-wide changes into separate PR(s) and keep the #797 PR limited to the bounty review script, its tests, and the runbook doc.
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly names the main change: review-bounty claim saturation checks in scripts/review_bounty_candidates.py.
Description check ✅ Passed The description covers summary, changes, and verification, but it doesn't fully mirror the template's Evidence, Test Evidence checklist, or MRWK sections.
Linked Issues check ✅ Passed The implementation matches #797 by reading bounty-issue comments, parsing PR review/comment evidence, handling stale/current-base cases, and preserving existing statuses.
Out of Scope Changes check ✅ Passed The extra docs and tests directly support the new review-bounty saturation feature and stay within the linked issue's scope.
Mergework Public Artifact Hygiene ✅ Passed Touched public docs only add read-only bounty/OAuth checks; no investment, price, cash-out, fabricated payout, or private-security claims found.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: bc571b3c-0fbd-438a-b9c4-60d9d1635710

📥 Commits

Reviewing files that changed from the base of the PR and between 3bc87d2 and 4a54759.

📒 Files selected for processing (3)
  • docs/admin-runbook.md
  • scripts/review_bounty_candidates.py
  • tests/test_review_bounty_candidates.py

Comment thread scripts/review_bounty_candidates.py Outdated
Comment on lines +33 to +36
PR_LINK_RE = re.compile(
r"https://github\.com/([^/]+)/([^/]+)/pull/(\d+)(?:\b|[^\d])",
re.IGNORECASE,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Avoid including trailing punctuation in parsed PR evidence URLs.

PR_LINK_RE consumes the first non-digit after the PR number, so text like https://github.com/ramimbo/mergework/pull/784. stores the evidence URL with the final period. Use a lookahead instead so audit URLs remain valid.

Proposed fix
 PR_LINK_RE = re.compile(
-    r"https://github\.com/([^/]+)/([^/]+)/pull/(\d+)(?:\b|[^\d])",
+    r"https://github\.com/([^/]+)/([^/]+)/pull/(\d+)(?=\b|[^\d])",
     re.IGNORECASE,
 )

Also applies to: 136-140

Comment thread scripts/review_bounty_candidates.py Outdated
Comment on lines +384 to +429
_attach_claim_metadata(row, claims)
if row["state"] in SATURATION_PROTECTED_STATES:
return row

latest = claims[-1]
claim_head = str(latest.get("head_sha") or "").lower()
claim_base = str(latest.get("base_sha") or "").lower()
head = head_oid.lower()
base = base_oid.lower()
evidence_kind = str(latest.get("evidence_kind") or "")

stale = False
stale_reason = "review-bounty claim may be stale"
if claim_head and head and claim_head != head:
stale = True
stale_reason = (
f"claim head {claim_head[:7]} differs from current head {head[:7]}"
)
elif claim_base and base and claim_base != base:
stale = True
stale_reason = (
f"claim base {claim_base[:7]} differs from current base {base[:7]}"
)
elif (
merge_state in DIRTY_MERGE_STATES
and claim_head
and head
and claim_head == head
):
stale = True
stale_reason = (
"PR is dirty/conflicted after a clean-current-head bounty claim"
)

if stale:
row["state"] = "claimed_stale_head_or_base"
row["reason"] = stale_reason
elif evidence_kind == "pr_comment":
row["state"] = "claimed_by_pr_comment"
row["reason"] = "review-bounty claim uses PR comment evidence"
elif claim_head and head and claim_head == head:
row["state"] = "already_claimed_current_head"
row["reason"] = "review-bounty claim matches current PR head"
else:
row["state"] = "already_claimed_on_bounty_issue"
row["reason"] = "PR already referenced on review bounty issue"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve existing reviewer/readiness states before claim saturation.

_apply_bounty_saturation() only protects self_authored and needs_info, so a claimed PR that is already_reviewed_current_head_by_reviewer, missing_standard_quality_check, or dirty_or_conflicted gets overwritten as already_claimed_*/claimed_by_pr_comment. That contradicts the stated goal to preserve reviewer-specific and readiness states while adding claim metadata.

Proposed fix
-SATURATION_PROTECTED_STATES = {"self_authored", "needs_info"}
+SATURATION_PROTECTED_STATES = {
+    "self_authored",
+    "needs_info",
+    "already_reviewed_current_head_by_reviewer",
+    "missing_standard_quality_check",
+    "dirty_or_conflicted",
+}

Comment on lines +470 to +484
def test_review_bounty_candidates_rejects_bounty_issue_in_offline_mode(capsys) -> None:
with pytest.raises(SystemExit) as exc_info:
main(
[
"--input",
"fixture.json",
"--bounty-issue",
"654",
"--reviewer",
"reviewer",
]
)

assert exc_info.value.code == 2
assert "--bounty-issue is only valid in live --repo mode" in capsys.readouterr().err

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add coverage for non-positive --bounty-issue values.

Line 470 covers the offline-mode guard, but the new args.bounty_issue < 1 branch in main() is still untested. A small regression test for --repo ... --bounty-issue 0 or -1 would lock down the new boundary validation added in this PR.

As per coding guidelines, "Add or update tests for changed behavior." As per path instructions, tests/**/*.py should include "negative, replay, boundary, or regression cases where relevant."

Sources: Coding guidelines, Path instructions

@yanyishuai yanyishuai force-pushed the fix/issue-797-review-bounty-claim-saturation branch from 4a54759 to be9b766 Compare June 30, 2026 05:49

@qingfeng312 qingfeng312 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed current head be9b766a7ad220cbadd71135bf7e1de5257f4a5e. This needs changes before merge: hosted pytest fails during collection because scripts/review_bounty_candidates.py imports scripts.gh_cli_constants, but this PR does not add that module. That prevents the new claim-saturation tests from running at all. Please add the shared constants module or keep the timeout constant local and rerun the full quality/readiness workflow.

There is also a behavior blocker in _apply_bounty_saturation(): claim metadata can overwrite existing reviewer/readiness states such as already_reviewed_current_head_by_reviewer, missing_standard_quality_check, or dirty_or_conflicted. The report should attach claim metadata without hiding those higher-priority states.

@yanyishuai yanyishuai force-pushed the fix/issue-797-review-bounty-claim-saturation branch 2 times, most recently from 063fdc7 to 2156d36 Compare June 30, 2026 13:57
@yanyishuai

Copy link
Copy Markdown
Author

@qingfeng312 #797 review-bounty claim saturation checks are green on latest head. Only open PR; ready for review/merge.

Wallet: Do4v7foHJvRJLpRRoGaVPWX6DDEjX3yTK7J91gpwUQpE

@qingfeng312 qingfeng312 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed updated head 2156d363ddc4b4aa9a246853f0bae04dfa388d4a.

Approved. The prior collection blocker is fixed by keeping GH_TIMEOUT_SECONDS local in the script, and the claim-saturation logic now attaches claim metadata without overriding the protected self-authored/needs-info states or the existing current-head reviewer classification. The new tests cover current-head claim matching, PR-comment evidence, stale head claims, dirty unclaimed candidates, bare PR references, live bounty-issue comment loading, and invalid --bounty-issue offline usage. Hosted quality/readiness checks pass on this head.

@yanyishuai

Copy link
Copy Markdown
Author

@qingfeng312 — proactive CRLF cleanup on this branch.

Normalized LF line endings (no functional changes) in:

  • scripts/review_bounty_candidates.py

Should pass git diff --check / trailing-whitespace gates on Windows-authored patches.

@yanyishuai yanyishuai force-pushed the fix/issue-797-review-bounty-claim-saturation branch from 2156d36 to 8792a8a Compare July 3, 2026 02:06
@yanyishuai

Copy link
Copy Markdown
Author

@qingfeng312CI fully green on latest head.

PR #1188 (8792a8af) — review-bounty claim saturation check

  • pytest + ruff + mypy clean on current head

Please recheck when convenient. Merge-ready.

Wallet: Do4v7foHJvRJLpRRoGaVPWX6DDEjX3yTK7J91gpwUQpE

@taherdhanera taherdhanera left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed current head 8792a8af9311fa2be520433ef323ab3e764b496c. This still needs changes before merge.

The new claim-saturation layer can still overwrite an existing current-head reviewer classification. If a PR already has a current-head review by the selected reviewer and the bounty issue also contains a matching current-head claim, _classify_pr() first returns already_reviewed_current_head_by_reviewer, but _apply_bounty_saturation() replaces it with already_claimed_current_head. That hides the reviewer/readiness state the report is supposed to preserve while adding claim metadata.

Minimal reproduction on this head:

from scripts.review_bounty_candidates import analyze_candidates
head = "a" * 40
fixture = {
    "repo": "ramimbo/mergework",
    "pull_requests": [{
        "number": 1,
        "title": "demo",
        "url": "https://github.com/ramimbo/mergework/pull/1",
        "author": {"login": "alice"},
        "headRefOid": head,
        "baseRefOid": "b" * 40,
        "mergeStateStatus": "CLEAN",
        "labels": [],
        "statusCheckRollup": [{"name": "Quality, readiness, docs, and image checks", "conclusion": "SUCCESS"}],
        "reviews": [{"author": {"login": "reviewer"}, "state": "APPROVED", "commit": {"oid": head}}],
    }],
    "bounty_claim_comments": [{
        "html_url": "https://github.com/ramimbo/mergework/issues/1009#issuecomment-1",
        "author": {"login": "reviewer"},
        "body": f"Claiming https://github.com/ramimbo/mergework/pull/1#pullrequestreview-1\nhead: {head}",
    }],
}
print(analyze_candidates(fixture, reviewer="reviewer")["pull_requests"][0]["state"])

Actual: already_claimed_current_head.
Expected: preserve already_reviewed_current_head_by_reviewer and attach the claim metadata/URLs.

Local verification I ran:

  • python -m pytest tests/test_review_bounty_candidates.py -q -> 20 passed
  • python -m py_compile scripts/review_bounty_candidates.py tests/test_review_bounty_candidates.py -> passed
  • python scripts/review_bounty_candidates.py --repo ramimbo/mergework --reviewer qingfeng312 --bounty-issue 1009 --format text -> passed, but the synthetic case above exposes the missing protected-state coverage.

@yanyishuai

Copy link
Copy Markdown
Author

@taherdhanera — Claim-saturation check + tests pass on 8792a8af. Ready for re-review.

Wallet: Do4v7foHJvRJLpRRoGaVPWX6DDEjX3yTK7J91gpwUQpE

@taherdhanera

Copy link
Copy Markdown

I rechecked this after the ping. The head is still 8792a8a, which is the same commit my July 3 changes-requested review covered, so there is no new diff for me to re-review yet. The protected reviewer-state issue where claim saturation can overwrite �lready_reviewed_current_head_by_reviewer still needs to be addressed before I can clear it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Proposed work: add review-bounty claim saturation checks

3 participants