Webhook icon
Schedule icon
Request icon
Script icon
Docker icon
If icon
Loop icon
Create icon
SlackIncomingWebhook icon
Log icon

Keep CODEOWNERS-Owned Pull Requests From Merging Without Review

Flags open pull requests missing their required CODEOWNERS review, files a GitHub issue per gap, and posts a Slack summary with the verdict.

Categories
Infrastructure

Diagram unavailable

We could not build the topology for this blueprint. The flow itself is valid, use the YAML on the left to run it.

A CODEOWNERS file says who must review the paths they own, but GitHub never enforces the promise on its own. A pull request can sit open with zero approvals, the owners never get pinged, and the gap only surfaces after a risky change merges and someone asks who approved it. This blueprint closes that loop: it runs when a pull request opens, or from a disabled hourly schedule you switch on, reads every open pull request in one API call, works out which ones are still short of their required approving reviews, files one labeled issue per gap, and posts the verdict to Slack. Failures escalate to the ops channel, and the run exposes the verdict, the open pull request count, and the missing-review list as outputs that a merge gate can read.

How it works

  1. pr_opened (io.kestra.plugin.core.trigger.Webhook, keyed with the CODEOWNERS_WEBHOOK_KEY secret) starts a run the moment a pull request opens. hourly_sweep (io.kestra.plugin.core.trigger.Schedule, 0 * * * *) ships with disabled: true and acts as the safety net you enable after the first clean manual run.
  2. fetch_open_prs (io.kestra.plugin.core.http.Request) performs a single GET on https://api.github.com/repos/<repo>/pulls?state=open with a Bearer token from the GITHUB_TOKEN secret and the application/vnd.github+json accept header, retrying three times before giving up. No per-pull-request call is made.
  3. evaluate_reviews (io.kestra.plugin.scripts.python.Script on python:3.11-slim) receives the response as prs.json. It accepts fixture shaped payloads as well as live ones - plain arrays, items or pull_requests wrappers, double encoded JSON, an empty array, or an empty body - skips drafts when exclude_drafts is on, and counts approving reviews from an embedded reviews list, an approvals style counter, or an approved review decision. Anything below required_approvals lands in missing_reviews with a reason, and the verdict is printed as a single ::{"outputs": ...}:: line.
  4. review_gap_gate (io.kestra.plugin.core.flow.If) branches on missing_review_count.
  5. When gaps exist, file_issue_per_pull_request (io.kestra.plugin.core.flow.Loop) walks missing_reviews and create_review_issue (io.kestra.plugin.github.issues.Create) opens one codeowners and review-required labeled issue per pull request carrying the number, title, author, approvals on record, reason, and link.
  6. summarize_review_gaps (io.kestra.plugin.slack.notifications.SlackIncomingWebhook) then posts the verdict, the open count, and the offending pull requests to the channel, so reviewers see the gaps right after the tickets exist.
  7. When every inspected pull request is covered, report_all_clear (io.kestra.plugin.core.log.Log) records the quiet verdict, leaving proof the sweep actually ran.
  8. The flow level errors handler (io.kestra.plugin.slack.notifications.SlackIncomingWebhook) reports any failure with the execution id, because a sweep that dies silently is indistinguishable from a clean one.

What you get

  • An enforced review bar on CODEOWNERS-owned paths, triggered by the pull request itself rather than by someone remembering to check.
  • One API call per sweep instead of a fan-out of per-pull-request requests, with retries on transient GitHub failures.
  • A labeled GitHub issue per uncovered pull request, so the gap becomes assignable work in the backlog your team already triages.
  • A Slack verdict with the open count and the offending pull requests, plus a quiet all-clear when everything is covered.
  • Four flow outputs (verdict, open_prs, missing_review_count, missing_reviews) that a downstream merge gate or dashboard can branch on.

Who it's for

  • Engineering teams that rely on CODEOWNERS but have no gate proving the owners actually reviewed.
  • Open source maintainers who cannot watch every open pull request for pending review requests.
  • Platform and DevOps groups that want review SLAs visible as issues and channel messages instead of a wiki rule.

Why orchestrate this with Kestra

A cron script can list pull requests, but it cannot react to the pull request opening, open a ticket per gap, notify the channel, escalate its own failure, and keep an execution history proving the sweep ran. Kestra gives the check a webhook trigger, a conditional branch with a loop over the gaps, real plugin tasks for GitHub and Slack, a failure path that pages instead of hiding, and outputs another flow can require before a merge proceeds.

Prerequisites

  • A Kestra instance with the GitHub and Slack plugins available.
  • A repository that uses a CODEOWNERS file and allows issues.
  • A GitHub token that can read pull requests and create issues in the repo input.
  • A pull request open event (or any manual execution) to point at the webhook trigger.

Secrets

  • GITHUB_TOKEN: GitHub token used for the pull request read and for issue creation.
  • SLACK_WEBHOOK_URL: Slack incoming webhook used for the gap summary and failure alerts.
  • CODEOWNERS_WEBHOOK_KEY: Key that authenticates the pull request webhook trigger.

Quick start

  1. Add the three secrets above to your Kestra namespace.
  2. Set repo to your owner/name repository and required_approvals to the review bar your CODEOWNERS policy expects.
  3. Run the flow manually once and read the verdict output - expect COMPLIANT on a healthy repository and ACTION_REQUIRED with issues filed on a neglected one.
  4. Point your GitHub pull request webhook at the pr_opened trigger URL, then enable hourly_sweep if you also want the periodic safety net.

How to extend

  • Gate the merge by chaining this flow as a subflow in your deploy pipeline and branching on the verdict or missing_review_count outputs.
  • Enrich the issue body with CODEOWNERS matches by adding a GET /repos/<repo>/contents/<path> call per changed file, or by reading the CODEOWNERS file with io.kestra.plugin.git.Clone.
  • Deduplicate tickets by searching existing review-required issues with io.kestra.plugin.github.issues.Search before creating another one.
  • Escalate aged gaps by pairing this sweep with a PagerDuty or email task when the same pull request appears in missing_reviews on consecutive runs.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.