Skip to content

Explain CODEOWNERS reviewer requests on each PR - #19

Merged
bryanbeverly merged 3 commits into
mainfrom
bryan/pr-domain-reviewer-reasons
Jul 8, 2026
Merged

bryanbeverly merged 3 commits into
mainfrom
bryan/pr-domain-reviewer-reasons

Conversation

@bryanbeverly

@bryanbeverly bryanbeverly commented Jul 7, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Engineers are frequently unsure why a specific team was added as a required reviewer, especially after the team/CODEOWNERS reorganization. This extends the reusable PR labeler (pr_labeler.py) to publish a managed section on each PR explaining it, and lets each repo choose where that section goes via a new reasons_target input.

  • Adds domain_reasons() which, using the existing last-match-wins CODEOWNERS matcher, maps each owning team slug to the files (and matching pattern) that requested it.
  • Adds render_domain_reasons() which renders a > [!IMPORTANT] callout (purple, distinct from Bugbot's [!NOTE]) listing every team CODEOWNERS requested for review by its raw slug (matching the "Reviewers" sidebar), a short charter for known domains plus the eng-leads catch-all, and the files/patterns that matched (sorted, capped at 10 per team).
  • Adds a reasons_target workflow input choosing where the section is published:
    • comment (default): a single sticky PR comment, created once and edited in place on later runs, and deleted if the PR no longer touches any owned files.
    • body: a managed region of the PR description between DOMAIN_REVIEW_REASONS:START/END markers, rewritten in place (quiet — editing a description does not notify subscribers).
  • The rendering is shared by both targets; the only visual difference is the body variant keeps a leading horizontal rule to separate the block from the author's text, while the comment variant omits it (nothing precedes it).
  • Efficiency: dedupes CODEOWNERS matching to a single pass (domain_slugs = set(reasons), removing domains_for_pr) and folds the changed-file fetch into fetch_pr to save a gh call per PR.

No permission changes: the reusable workflow already grants contents: read (CODEOWNERS) and pull-requests: write (labels + body + comments).

Implementation notes

  • Body path: upsert_managed_section() inserts/replaces/removes the marked block idempotently.
  • Comment path: fetch_pr_comments() lists comments via gh api --paginate --jq '.[] | {id, body}' (NDJSON; numeric REST id for edit/delete); find_reasons_comment() locates our sticky comment by marker; plan_comment_action() decides create/update/delete/no-op (normalizing CRLF + whitespace so round-tripped bodies don't churn); apply_comment() POST/PATCH/DELETEs (no-op on dry runs).
  • main() reads REASONS_TARGET (unknown value warns and falls back to comment) and branches accordingly; the appropriate [comment: ...] / [body: ...] note is logged whenever a change would happen, even on dry runs.

Coexistence / safety

  • Distinct markers from Bugbot's CURSOR_SUMMARY; each tool only rewrites its own region/comment.
  • Deterministic output (ordering + sorted files) means the "edit only on change" guard converges — no rewrite churn, and edits made with GITHUB_TOKEN don't re-trigger the workflow.
  • The block text can't trip the risk/urgent/complexity parsers.
  • Switching a repo's target does not clean up the other target's leftover section; in practice this is a non-issue since comment is the default from the start.

Rollout / backfill

Callers inherit the comment default with no changes; a repo can opt into the quiet description mode with reasons_target: body. On the first event-driven run, each open PR gets the section on its next synchronize. To backfill existing open PRs, dispatch the labeler with pr_number: all — run a dry run first (pr_number: all, dry_run: true) and eyeball the logged per-PR plans / notes before the real run. trufflehog OSS remains excluded from labeling.

Test plan

  • ruff check .github/scripts and ruff format --check .github/scripts pass.
  • pytest .github/scripts/test_pr_labeler.py — 113 passing, incl. coverage for domain_reasons (multi/co-owned/unowned/catch-all), render_domain_reasons (ordering, charter vs. bare slug, file cap, backtick neutralization, link vs. plain, leading-rule on/off), upsert_managed_section (insert/replace/remove, malformed markers, round-trip idempotency), and the comment helpers find_reasons_comment / plan_comment_action (create/update/delete/no-op + CRLF idempotency).
  • End-to-end on trufflesecurity/thog#5685 (comment mode): create → idempotent re-run → update (dropping the leading rule) → idempotent re-run; verified exactly one sticky comment and that the callout renders with slugs matching the Reviewers sidebar and a resolving CODEOWNERS link.

Made with Cursor

Engineers are often unsure why a team was added as a required reviewer
after the team/CODEOWNERS reorg. The PR labeler now writes a managed,
marker-delimited section into each PR description listing every team
CODEOWNERS requested for review, the files/patterns that matched, and a
short charter for known domains (plus the eng-leads catch-all).

The section is idempotent (deterministic ordering, sorted+capped file
lists) and only written when the body actually changes, so it coexists
with Bugbot's CURSOR_SUMMARY and never loops. Also dedupes the CODEOWNERS
matching to a single pass and folds the changed-file fetch into fetch_pr
to save a gh call per PR.

Co-authored-by: Cursor <cursoragent@cursor.com>
@bryanbeverly
bryanbeverly requested a review from a team July 7, 2026 21:15

@cursor cursor 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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 803527e. Configure here.

Comment thread .github/scripts/test_pr_labeler.py
Bugbot flagged the constant as dead code. Parse it through
domain_reasons so the unknown-team case gets real end-to-end coverage
instead of an inline reasons dict.

Co-authored-by: Cursor <cursoragent@cursor.com>
@bryanbeverly

Copy link
Copy Markdown
Contributor Author

The dry run was performed against thog with 0 errors.

The "real" run was performed against trufflesecurity/thog#5685.

@martinlocklear

Copy link
Copy Markdown
Contributor

I think the intent here is great, but I find things that edit my PR descriptions really... not comfortable. Would you be open to putting this new information into a PR comment instead of the description?

@bryanbeverly

Copy link
Copy Markdown
Contributor Author

I think the intent here is great, but I find things that edit my PR descriptions really... not comfortable. Would you be open to putting this new information into a PR comment instead of the description?

It uses the same block commenting that Cursor Bugbot uses (and that the review checklists use to prevent duplicate comments), so it would only be added to the bottom. I'm not a huge fan of the comments because they feel detached and more easily lost in the noise to me, but I'm also not the primary audience for the work here. Maybe we can do a poll?

@martinlocklear

martinlocklear commented Jul 7, 2026 •

Copy link
Copy Markdown
Contributor

I think the intent here is great, but I find things that edit my PR descriptions really... not comfortable. Would you be open to putting this new information into a PR comment instead of the description?

It uses the same block commenting that Cursor Bugbot uses (and that the review checklists use to prevent duplicate comments), so it would only be added to the bottom. I'm not a huge fan of the comments because they feel detached and more easily lost in the noise to me, but I'm also not the primary audience for the work here. Maybe we can do a poll?

Yeah - I found cursor bugbot adjusting my PR descriptions intrusive as well - but no one asked me at that point (I did grumbl, but that's lost to slack history). I don't know what otehr folks folks think, but the more fingers we have in that pie, the more likely we are to step on each other's toes. The comments are log-like and designed to support additive elements to a PR.

@sysread sysread 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.

This is great! I probably would have made it post a comment instead of modifying the PR description myself, but it seems innocuous and should only maintain the section it governs. We might want to add a comment to the PR description output that modifying those placeholders will break the integration.

Add a `reasons_target` input to the reusable PR labeler (default
"comment") so the domain-review-reason section can be published as a
single sticky PR comment instead of a managed region of the PR body.

Comment mode creates the comment once, edits it in place on later runs,
and deletes it when the PR no longer touches any owned files; equality
checks normalize CRLF/whitespace so round-tripped bodies don't churn.
The leading horizontal rule is dropped in comment mode (nothing precedes
it) and kept in body mode. Body mode is unchanged and available via
`reasons_target: body`.

Co-authored-by: Cursor <cursoragent@cursor.com>
@bryanbeverly bryanbeverly changed the title Explain CODEOWNERS reviewer requests in the PR body Explain CODEOWNERS reviewer requests on each PR Jul 8, 2026
@bryanbeverly

bryanbeverly commented Jul 8, 2026 •

Copy link
Copy Markdown
Contributor Author

Why not both? I left the body update in place, but also added a comment mode and made that the default. If you wanted a repo to put this data in the PR body, you'd make this change to the workflow (.github/workflows/pr-labeler.yml):

jobs:
  label:
    uses: trufflesecurity/.github/.github/workflows/pr-labeler-reusable.yml@main
    with:
      reasons_target: body

Warning

Because the system now adds comments, if we did a backfill run (updating all current open PRs), it would also send a notification to each PR owner for every open PR.

@bryanbeverly
bryanbeverly merged commit fc05b94 into main Jul 8, 2026
4 checks passed
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.

3 participants