Skip to content

Refs #406: Skip stale bounty refs in webhook payouts#560

Merged
ramimbo merged 1 commit into
ramimbo:mainfrom
prettyboyvic:codex/bounty-406-next-scan
May 28, 2026
Merged

Refs #406: Skip stale bounty refs in webhook payouts#560
ramimbo merged 1 commit into
ramimbo:mainfrom
prettyboyvic:codex/bounty-406-next-scan

Conversation

@prettyboyvic
Copy link
Copy Markdown
Contributor

@prettyboyvic prettyboyvic commented May 28, 2026

Summary

  • make accepted-label webhook payout selection skip closed or exhausted bounty references when a later referenced bounty is still payable
  • preserve the old failure behavior when no referenced bounty has award capacity
  • add a regression for a PR body that mentions a closed stale bounty before the current open bounty target

Why

A valid accepted PR body can mention an older issue with a closed MRWK bounty, then identify the current bounty target later in the body. Before this change, the webhook picked the first matching bounty row and failed with bounty_is_not_open instead of paying the later open bounty.

Distinctness for Bounty #406: this is not another parser-form change or native/internal bounty-id guard. It handles the selection step after parsing real GitHub bounty references, specifically closed/exhausted refs before a payable ref.

Validation

  • ./.venv/Scripts/python.exe -m pytest tests/test_webhooks.py -q -> 19 passed
  • ./.venv/Scripts/python.exe -m pytest tests/test_security.py::test_admin_page_renders_safe_webhook_events_for_cookie_admin tests/test_security.py::test_admin_webhook_events_api_lists_and_filters_processing_outcomes tests/test_security.py::test_signed_webhook_with_invalid_json_is_rejected tests/test_security.py::test_duplicate_webhook_delivery_with_different_payload_is_rejected tests/test_security.py::test_webhook_rejects_unapproved_accepted_labeler -q -> 5 passed
  • ./.venv/Scripts/python.exe -m pytest -q -> 415 passed
  • ./.venv/Scripts/python.exe -m ruff check . -> passed
  • ./.venv/Scripts/python.exe -m ruff format --check . -> 79 files already formatted
  • ./.venv/Scripts/python.exe -m mypy app -> success, no issues found in 30 source files
  • git diff --check -> clean

Refs #406

Summary by CodeRabbit

Bug Fixes

  • GitHub webhook now intelligently selects bounties that remain eligible for additional awards when processing labeled pull requests, improving reward assignment accuracy.

Tests

  • Added comprehensive test coverage to verify correct webhook behavior when multiple bounties exist with varying eligibility statuses.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 28, 2026

📝 Walkthrough

Walkthrough

The pull request modifies the GitHub webhook handler to make bounty selection award-aware. A new helper function identifies bounties eligible to receive additional awards, and the webhook's accepted-label handler now prefers such bounties while retaining fallback behavior for cases where no open bounties with award capacity exist.

Changes

Bounty Award Eligibility

Layer / File(s) Summary
Bounty acceptance eligibility check
app/webhooks/github.py
Import Bounty model and add _bounty_accepts_awards(bounty: Bounty) -> bool helper to identify bounties with status == "open" and awards_paid < max_awards.
Bounty selection preference logic
app/webhooks/github.py
Update bounty lookup loop in accepted-label processing to track the first candidate found and prefer the first bounty that _bounty_accepts_awards, with fallback to the first bounty found if no candidates are eligible.
Test coverage for bounty filtering
tests/test_webhooks.py
Add close_bounty to ledger service imports and introduce test_accepted_pr_label_skips_closed_bounty_for_later_open_reference to verify the webhook correctly skips closed bounties and processes open bounties with remaining award capacity.
🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Bounty Pr Focus ⚠️ Warning PR scope matches stated files, but review comment requesting an exhausted-before-open test case was not addressed; only closed-before-open test was added. Add a regression test where first bounty has awards_paid==max_awards and later bounty is still payable, to fully address the review feedback on bounty selector targets.
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Refs #406: Skip stale bounty refs in webhook payouts' is concrete, names the changed surface (webhook payout selection logic), and aligns with the main change in the changeset.
Description check ✅ Passed The description covers the summary, rationale, intended changes, validation evidence, and includes the bounty reference. All required template sections are present and substantively completed.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Mergework Public Artifact Hygiene ✅ Passed PR modifies only app code and tests; no changes to docs or public artifacts. Code contains no investment, price, cash-out, or security claims.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ae4b1ac5-a83b-433b-8f1a-89e69565e4f5

📥 Commits

Reviewing files that changed from the base of the PR and between d8532d4 and 74bf772.

📒 Files selected for processing (2)
  • app/webhooks/github.py
  • tests/test_webhooks.py

Comment thread tests/test_webhooks.py
Comment on lines +849 to +913
def test_accepted_pr_label_skips_closed_bounty_for_later_open_reference(
sqlite_url: str,
) -> None:
create_schema(sqlite_url)
body = json.dumps(
{
"action": "labeled",
"label": {"name": "mrwk:accepted"},
"pull_request": {
"number": 13,
"html_url": "https://github.com/ramimbo/mergework/pull/13",
"body": "Fixes #3\n\nBounty #4",
"user": {"login": "reviewer"},
},
"repository": {"full_name": "ramimbo/mergework"},
"sender": {"login": "maintainer"},
},
separators=(",", ":"),
).encode()

with session_scope(sqlite_url) as session:
ensure_genesis(session)
closed_bounty = create_bounty(
session,
repo="ramimbo/mergework",
issue_number=3,
issue_url="https://github.com/ramimbo/mergework/issues/3",
title="Closed stale bounty",
reward_mrwk="40",
acceptance="Already closed bounty should not block later open refs.",
)
close_bounty(
session,
bounty_id=closed_bounty.id,
closed_by="maintainer",
reference="https://github.com/ramimbo/mergework/issues/3#close",
)
create_bounty(
session,
repo="ramimbo/mergework",
issue_number=4,
issue_url="https://github.com/ramimbo/mergework/issues/4",
title="Current open bounty",
reward_mrwk="25",
acceptance="Accepted PR can mention stale refs before the bounty target.",
)

result = handle_github_webhook(
sqlite_url,
{
"X-GitHub-Delivery": "delivery-closed-then-open-pr",
"X-GitHub-Event": "pull_request",
"X-Hub-Signature-256": _signature("secret", body),
},
body,
"secret",
)

assert result["status"] == "paid"
with session_scope(sqlite_url) as session:
assert get_balance(session, "github:reviewer") == 25_000_000
event = session.get(WebhookEvent, "delivery-closed-then-open-pr")
assert event is not None
assert event.processed_status == "paid"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add an exhausted-before-open regression case.

This test proves the closed-then-open path, but the selector change also targets exhausted bounties. Please add a companion case where the first referenced bounty is exhausted and a later referenced bounty is still payable.

As per coding guidelines, tests/**/*.py: Do not request docstrings. Focus on whether tests prove the changed behavior and include negative, replay, boundary, or regression cases where relevant.

@tinyopsstudio
Copy link
Copy Markdown

Reviewed PR #560 at current head 74bf772e65de9076013fe2c8fd1645387c8b13ae.

No blocker found. The selection change is scoped to the accepted-label bounty lookup: it keeps the first matching bounty as the legacy fallback, but now prefers the first referenced bounty that is still open and has award capacity. That lets a PR body mention an older closed or exhausted bounty before the current payable bounty without changing the existing failure behavior when none of the referenced bounties can pay.

I also checked the exhausted-before-open case raised by CodeRabbit with a local smoke scenario: create issue #3 with max_awards=1, pay it once so it becomes exhausted/paid, create issue #4 as open, then process an accepted PR body containing Fixes #3 followed by Bounty #4. The webhook returned paid, credited github:reviewer with 25_000_000, and recorded the webhook event as paid.

Validation run on this head:

  • PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 ./.venv/bin/python -m pytest tests/test_webhooks.py -q -> 19 passed
  • PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 ./.venv/bin/python -m pytest tests/test_security.py::test_admin_page_renders_safe_webhook_events_for_cookie_admin tests/test_security.py::test_admin_webhook_events_api_lists_and_filters_processing_outcomes -q -> 2 passed
  • PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 ./.venv/bin/python -m pytest -q -> 415 passed
  • ./.venv/bin/python -m ruff check app/webhooks/github.py tests/test_webhooks.py -> passed
  • ./.venv/bin/python -m ruff format --check app/webhooks/github.py tests/test_webhooks.py -> 2 files already formatted
  • PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 ./.venv/bin/python -m mypy app -> success
  • PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 ./.venv/bin/python scripts/docs_smoke.py -> docs smoke ok
  • git diff --check origin/main...HEAD -> clean

GitHub readback shows the PR is open, non-draft, CLEAN, and the quality/readiness workflow is successful.

Copy link
Copy Markdown

@xingxi0614-cpu xingxi0614-cpu left a comment

Choose a reason for hiding this comment

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

Reviewed PR #560 current head 74bf772e65de9076013fe2c8fd1645387c8b13ae.

Verdict: no blocker found in this webhook payout bounty-selection slice.

Evidence:

  • Inspected app/webhooks/github.py: the accepted-label handler now records the first linked bounty it finds, but prefers the first linked bounty that is still open and has award capacity. This prevents an older closed/exhausted bounty reference from blocking a later open bounty reference in the same PR body.
  • Inspected tests/test_webhooks.py: the new regression creates a closed stale bounty for issue #3, an open bounty for issue #4, labels a PR body containing Fixes #3 before Bounty #4, and verifies the payout goes to the open bounty instead of failing on the stale one.
  • Confirmed the diff is focused to app/webhooks/github.py and tests/test_webhooks.py.
  • Confirmed the PR is open, non-draft, and current head is 74bf772e65de9076013fe2c8fd1645387c8b13ae.

Validation:

  • uv run --extra dev python -m pytest tests/test_webhooks.py -q -> 19 passed
  • uv run --extra dev python -m pytest -q -> 415 passed
  • uv run --extra dev ruff check app/webhooks/github.py tests/test_webhooks.py -> passed
  • uv run --extra dev ruff format --check app/webhooks/github.py tests/test_webhooks.py -> 2 files already formatted
  • uv run --extra dev python -m mypy app/webhooks/github.py -> success
  • uv run --extra dev python scripts/docs_smoke.py -> docs smoke ok
  • git diff --check origin/main...HEAD -> clean

No secrets, private data, cookies, wallet material, signatures, production mutation, private security details, price claims, liquidity claims, exchange claims, bridge claims, or off-ramp promises were used.

Copy link
Copy Markdown
Owner

@ramimbo ramimbo left a comment

Choose a reason for hiding this comment

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

Maintainer review: no blocker found. The change is focused to webhook bounty selection, keeps fallback behavior when no payable bounty is referenced, and has a regression for stale closed refs before a payable ref. This is safe to include in the low-conflict cleanup batch.

@ramimbo ramimbo merged commit 3b4272b into ramimbo:main May 28, 2026
2 checks passed
@ramimbo ramimbo added mrwk:accepted Maintainer accepted for payout mrwk:paid Ledger payment recorded labels May 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mrwk:accepted Maintainer accepted for payout mrwk:paid Ledger payment recorded

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants