Skip to content

Watchflow Rules check returns stale PR data from webhooks #72

@dkargatzis

Description

@dkargatzis

Summary

When a PR is pushed (synchronize action) and a review is re-requested around the same time, the CODEOWNERS condition in Watchflow Rules incorrectly flags missing code owners — even though the review was already requested/approved.

Root Cause

Two webhooks fire in quick succession:

  1. pull_requestaction: synchronize (from the push)
  2. pull_requestaction: review_requested (from the re-request)

The rule engine evaluates when it receives the first webhook (synchronize). At that point, the requested_reviewers in the webhook payload hasn't been updated yet to reflect the new review request — GitHub processes these as separate events, and the enricher reads directly from the payload.

This causes a race condition where:

  • The condition sees stale requested_reviewers (without the code owner)
  • The condition sees no reviews yet (the approval comes later via a different webhook)
  • The check fails with "code owners missing" even though the code owner has already been requested/approved

Impact

  • False positives — PRs blocked unnecessarily
  • Confusion — Authors and reviewers see mismatched state between GitHub UI and Watchflow Rules check
  • Manual workaround — Requires re-pushing or waiting for the webhook to settle

Affected Rules

  • PathHasCodeOwnerCondition (requires code owners for modified paths)
  • RequireCodeOwnerReviewersCondition (requires code owners as reviewers)

Expected Behavior

The condition should see the current state of requested_reviews and reviews — not the stale snapshot from the webhook payload.

Proposed Fixes

Option 1: Re-fetch PR details in the enricher (Recommended)

In the PR enricher, after fetching the initial data from the webhook payload, make an additional GitHub API call to fetch the current PR state:

async def enrich(self, event: dict, context: dict) -> dict:
    # ... existing enrichment from webhook payload ...
    
    # Fetch fresh PR state for up-to-date requested_reviewers
    current_pr = await self.github.get_pull_request(owner, repo, number)
    context["requested_reviewers"] = current_pr.requested_reviewers
    context["reviews"] = current_pr.reviews

This ensures any condition reading from PR details gets fresh data, not just requested_reviewers but also assignees, labels, etc.

Option 2: Defensive check in CODEOWNERS conditions

The condition could also check the reviews list for users who have already submitted a review (approved, changes_requested, etc.):

def _has_code_owner_approval(self, event, context):
    # Existing check for requested_reviewers ...
    
    # Also check users who already approved
    reviews = context.get("reviews", [])
    for review in reviews:
        if review["state"] == "APPROVED" and self._is_code_owner(review["author"]):
            return True

This is more defensive but only solves the specific case.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions