Skip to content

ci(assign-reviewers): self-enroll this repo + seed the expertise map from approval history#78

Open
mattmillerai wants to merge 2 commits into
mainfrom
matt/self-enroll-assign-reviewers
Open

ci(assign-reviewers): self-enroll this repo + seed the expertise map from approval history#78
mattmillerai wants to merge 2 commits into
mainfrom
matt/self-enroll-assign-reviewers

Conversation

@mattmillerai

Copy link
Copy Markdown
Contributor

17 open PRs in this repo had no assignee, so nothing routed them to anyone who could approve them. This repo has shipped an expertise-aware, load-balanced router since BE-1943 and never enrolled itself in it.

Worth stating up front, because the name misleads: assign-reviewers.yml writes the ASSIGNEE field, not reviewer requests. From its own header — "this org routes/alerts people via the ASSIGNEE field". That is precisely the routing that was missing here.

Why the map is seeded from approvals, not git history

Nearly every commit on main here has a single author, so authorship carries no routing signal at all. Scoring it would put one name in every bucket. The buckets are instead populated from who has actually approved merged PRs in the matching area:

approver merged-PR approvals area
wei-hai 9 bump-callers, CI plumbing, assign-*
huntcsg 3 cursor-review internals
deepme987 3 bump-callers wire-bot, assign-reviewers
sundar-svg 2 cursor-review runs_on / self-hosted
synap5e 1 groom (builder PR body, BE-4346)
luke-mino-altherr 1 groom (direct-CLI agent phases, BE-4214)
bigcat88 1 groom (caller permissions)

This is a prerequisite for #63, not a duplicate of it

refresh-reviewers (#63, BE-4116) treats the committed rules: globs as its bucket definitions and only re-scores membership — it cannot bootstrap a map from nothing. Two warnings are recorded in the file itself:

  1. It scores recency-decayed commit authorship, so on this repo its first drift PR will want to collapse buckets toward the single dominant committer. That is a history artifact, not a routing improvement — read that PR, don't rubber-stamp it.
  2. The globs are therefore the durable part of the file. They outlive the names.

default_pool is load-bearing

Selection drops the PR author, so a bucket whose only member is the author empties. default_pool is three proven approvers and deliberately excludes the person who opens most PRs here.

Verification

The reusable parses reviewers.yml with a hand-rolled minimal YAML parser (js-yaml isn't available inside github-script), so standard YAML validity is not sufficient. I extracted that parser and its globToRegExp / matchesAny verbatim from assign-reviewers.yml and drove the committed map through them:

  • 7 rules, every one with non-empty paths + reviewers; default_pool parsed correctly.
  • 17 representative repo paths all route to the intended bucket; an unmatched path correctly falls through to default_pool.
  • Retro-validated against the 17 live assignments: 15/17 of the assignees fall inside what this map would pick, and 0 fell through to default_pool.
  • actionlint clean; all 8 referenced logins confirmed repo collaborators (GitHub silently drops an unassignable login, so this is worth checking rather than assuming).

The two deviations are deliberate human overrides, not map errors:

PR assigned map would pick why
#71 guill groom bucket routed as a privilege-boundary review, not a groom-domain one
#61 sundar-svg guill, wei-hai pure load-spreading on a one-line binary deletion

That retro pass is also what caught a real gap: ci-assign-reviewers.yml does not match assign-*.yml, so this repo's own routing caller would have fallen through to default_pool. It now names itself.

num_reviewers: 1 matches how PRs here are actually tracked — one named owner, not a pair. Easy to raise if single-owner PRs start stalling.

ELI5

Pull requests here were piling up with nobody's name on them, so nobody knew they were meant to look. The repo already had a robot that puts the right person's name on a PR based on which files it touches — it had just never been switched on for itself.

This switches it on and gives it the phone book. The phone book is built from who has actually approved this kind of change before, not from who wrote the code, because one person wrote nearly all the code here and that tells you nothing about who should check it.

🤖 Generated with Claude Code

…from approval history

17 open PRs here had gone unassigned, so nothing routed them to anyone who could
approve. This repo has shipped an expertise-aware, load-balanced router since
BE-1943 and never enrolled itself in it.

Despite its name assign-reviewers.yml writes the ASSIGNEE field, not reviewer
requests — "this org routes/alerts people via the ASSIGNEE field" (its header) —
which is exactly the routing that was missing.

WHY THE MAP IS SEEDED FROM APPROVALS, NOT GIT HISTORY: nearly every commit on
main here has a single author, so authorship carries no routing signal at all.
The buckets are instead populated from who has actually APPROVED merged PRs in
the matching area: wei-hai 9, huntcsg 3, deepme987 3, sundar-svg 2, synap5e 1,
luke-mino-altherr 1, bigcat88 1.

This also has to exist before refresh-reviewers (#63, BE-4116) is useful: that
generator treats the committed `rules:` globs as its BUCKET DEFINITIONS and only
re-scores membership, so it cannot bootstrap a map from nothing. Two warnings are
recorded in the file: it scores commit authorship (so on this repo its first
drift PR will want to collapse buckets toward the single dominant committer —
read it, don't rubber-stamp it), and the globs are therefore the durable part.

`default_pool` is deliberately three proven approvers and deliberately excludes
the person who opens most PRs here: selection drops the PR author, so a bucket
whose only member is the author empties, and default_pool is the real safety net.

Verification — the reusable parses reviewers.yml with a hand-rolled minimal YAML
parser (js-yaml isn't available in github-script), so standard YAML validity is
not sufficient. Extracted that parser AND its globToRegExp/matchesAny verbatim
and drove the committed map through them:

- 7 rules, every one with non-empty paths + reviewers; default_pool parsed.
- 17 representative repo paths all route to the intended bucket; an unmatched
  path correctly falls through to default_pool.
- Retro-validated against the 17 live assignments: 15/17 of the assignees fall
  inside what this map would pick, 0 fell through to default_pool. The two
  deviations are deliberate human overrides, not map errors — #71 went to guill
  as a privilege-boundary review rather than a groom-domain one, and #61 went to
  sundar-svg purely to spread load on a one-line binary deletion.
- That retro pass is what caught `ci-assign-reviewers.yml` not matching
  `assign-*.yml`; the caller now names itself so this repo's own routing config
  doesn't fall through to default_pool.
- actionlint clean; all 8 referenced logins confirmed as repo collaborators
  (GitHub silently drops an unassignable login).

`num_reviewers: 1` matches how PRs here are actually tracked — one named owner.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@mattmillerai
mattmillerai marked this pull request as ready for review July 26, 2026 07:42
@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 9 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4363e4c0-1e07-41e6-a37a-e49baeef4efe

📥 Commits

Reviewing files that changed from the base of the PR and between 29a81ca and 7f6259e.

📒 Files selected for processing (3)
  • .github/reviewers.yml
  • .github/workflows/ci-assign-reviewers.yml
  • AGENTS.md
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch matt/self-enroll-assign-reviewers
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch matt/self-enroll-assign-reviewers

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

@mattmillerai mattmillerai added the cursor-review Multi-model cursor review label Jul 26, 2026

@github-actions github-actions 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 Review — Consolidated panel

Triggered by @mattmillerai.

Found 5 finding(s).

Severity Count
🟠 High 1
🟡 Medium 1
🟢 Low 3

Panel: 8/8 reviewers contributed findings.

Comment thread .github/workflows/ci-assign-reviewers.yml
Comment thread .github/workflows/ci-assign-reviewers.yml
Comment thread .github/reviewers.yml
Comment thread .github/workflows/ci-assign-reviewers.yml Outdated
Comment thread .github/reviewers.yml
…ap glob gaps

Review-panel follow-ups on the self-enrollment caller:

- Fork PRs: `pull_request` withholds repository secrets from fork-originated
  runs, so CLOUD_CODE_BOT_PRIVATE_KEY arrives empty and the App-token mint hard
  fails — a red X on every external contribution for a routing decision that
  could not have been made anyway. Guard the job to same-repo heads so it skips
  cleanly, and correct the header, which claimed the App token made fork PRs
  work here. The one path that would actually route them, `pull_request_target`,
  is named in the header along with why it is not worth taking (privileged run
  against untrusted HEAD, while the map is read from the HEAD sha).

- Add `reopened` to the trigger types: a PR closed while still unrouted (e.g.
  its `opened` run failed) otherwise never gets a second routing event. Safe to
  re-fire — the reusable skips any PR that already has a non-author assignee.

- reviewers.yml: `test-bump-callers.yml` does not match `bump-*.yml`, and
  `ci-detect-unreviewed-merge.yml` is reached by no glob in the housekeeping
  bucket; both fell through to default_pool. Name them, as every other bucket
  already names its own `test-*.yml` / `ci-*.yml`.

- AGENTS.md: list the new caller alongside the other self-enrollment callers
  covered by the "pin a merged-main SHA, never a local path" rule.

Verified by driving the committed map through the reusable's own hand-rolled
YAML parser and globToRegExp (extracted verbatim from assign-reviewers.yml):
7 rules, all non-empty, and all 22 workflow files now route to an intended
bucket with nothing falling through. actionlint clean; agents-md-integrity
checker passes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cursor-review Multi-model cursor review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants