Repository navigation
Explain CODEOWNERS reviewer requests on each PR - #19
Conversation
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>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ 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.
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>
|
The dry run was performed against thog with 0 errors. The "real" run was performed against trufflesecurity/thog#5685. |
|
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
left a comment
There was a problem hiding this comment.
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>
|
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 ( 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. |

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 newreasons_targetinput.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.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 theeng-leadscatch-all, and the files/patterns that matched (sorted, capped at 10 per team).reasons_targetworkflow 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 betweenDOMAIN_REVIEW_REASONS:START/ENDmarkers, rewritten in place (quiet — editing a description does not notify subscribers).domain_slugs = set(reasons), removingdomains_for_pr) and folds the changed-file fetch intofetch_prto save aghcall per PR.No permission changes: the reusable workflow already grants
contents: read(CODEOWNERS) andpull-requests: write(labels + body + comments).Implementation notes
upsert_managed_section()inserts/replaces/removes the marked block idempotently.fetch_pr_comments()lists comments viagh 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()readsREASONS_TARGET(unknown value warns and falls back tocomment) and branches accordingly; the appropriate[comment: ...]/[body: ...]note is logged whenever a change would happen, even on dry runs.Coexistence / safety
CURSOR_SUMMARY; each tool only rewrites its own region/comment.GITHUB_TOKENdon't re-trigger the workflow.commentis the default from the start.Rollout / backfill
Callers inherit the
commentdefault with no changes; a repo can opt into the quiet description mode withreasons_target: body. On the first event-driven run, each open PR gets the section on its nextsynchronize. To backfill existing open PRs, dispatch the labeler withpr_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.trufflehogOSS remains excluded from labeling.Test plan
ruff check .github/scriptsandruff format --check .github/scriptspass.pytest .github/scripts/test_pr_labeler.py— 113 passing, incl. coverage fordomain_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 helpersfind_reasons_comment/plan_comment_action(create/update/delete/no-op + CRLF idempotency).Made with Cursor