Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 88f4f5b8-b61a-4f5b-845f-dcc8c82ce55e
11 KiB
Review routing, ownership, and SLAs
This page describes how pull requests in Awesome Copilot are assigned to reviewers, how quickly a first review is expected, and what happens when that target is missed. It covers Phase 1 of github/awesome-copilot#4184.
Ownership (CODEOWNERS)
The root CODEOWNERS file assigns every path to a team, not to an individual.
| Path | Owning team |
|---|---|
* (default) |
@github/awesome-copilot-core-maintainers |
/extensions/ |
@github/awesome-copilot-canvas-reviewers |
/plugins/ |
@github/awesome-copilot-plugin-reviewers |
/.github/plugin/ |
@github/awesome-copilot-plugin-reviewers |
/agents/, /instructions/, /skills/, /hooks/, /workflows/ |
@github/awesome-copilot-content-reviewers |
/.github/ (including GitHub Actions workflows), /eng/ |
@github/awesome-copilot-core-maintainers |
Copilot workshop content under /website/ |
@github/awesome-copilot-workshop-reviewers |
GitHub applies the last matching rule, so the workshop entries and the /.github/plugin/ entry override broader rules for their paths. When you edit CODEOWNERS by hand:
- Keep the
*rule and the domain team block at the top of the file. - Never add an individual user to the
*rule. - Add more-specific path owners after the broader rules they override.
Reviewer pools (.github/review-routing.yml)
.github/review-routing.yml is the single source of truth for automatic review requests. Workflows never hard-code reviewer handles.
| Key | Purpose |
|---|---|
dry_run |
When true, workflows log their planned actions to the job summary but make no changes. |
sla.first_review_business_days |
Business days after routing until a first review is due (default 2). |
sla.escalation_business_days |
Business days after routing until escalation to the core pool (default 4). |
sla.holidays |
Optional list of YYYY-MM-DD UTC dates excluded from business-day math. |
labels |
Names of the labels the routing workflows manage. |
pools.<name>.team |
Team in <org>/<team-slug> form. It is requested when no individual is available, and the escalation pool's team is always requested on escalation. |
pools.<name>.reviewers |
Individual GitHub logins that routing chooses from. |
pools.<name>.backup |
Individuals requested when a review is overdue. |
routes |
Ordered list mapping intent labels to pools. The first route whose label is on the PR wins. |
default_pool |
Pool used when no route matches, for example a docs-only PR. |
escalation_pool |
Pool that receives escalations and serves as the last-resort fallback. |
unavailable |
Logins that are temporarily away. They are never selected. |
skip_authors, skip_labels |
PRs by these authors, or with these labels, are not routed or escalated. |
Current routes, in priority order:
| Intent label | Pool |
|---|---|
workshops |
workshops |
canvas-extension |
canvas |
external-plugin, plugin |
plugin |
skills, agent, instructions, workflow, hooks |
content |
website-update |
core-maintainers |
Intent labels are applied by .github/workflows/label-pr-intent.yml and label-pr-intent-writer.yml.
Updating the pools
- Edit
.github/review-routing.yml. Keep at least three reviewers per pool, with coverage across time zones. - Validate the file with
node eng/review-routing.mjs validate, then run the unit tests withnode --test eng/review-routing.test.mjs. - Open a PR. The file is owned by the core-maintainer team.
To take a reviewer out of rotation temporarily, add their login to unavailable instead of removing them from every pool.
Routing
The Review Routing workflow (.github/workflows/review-routing.yml) runs when:
- Label PR Intent finishes for a PR. Routing uses that run's intent labels even if they have not been applied yet.
- A person adds the
needs-reviewerlabel. The read-only Review Routing Request workflow records the request, and Review Routing acts on it. - It is dispatched with a
pr_numberinput. Automation such as the/request-reviewcommand uses this path because labels added withGITHUB_TOKENdo not trigger other workflows. - The hourly schedule runs, or it is dispatched without a
pr_number. This sweep evaluates every open, non-draft PR. It routes PRs that are unrouted, that have theneeds-reviewerlabel, or whose pool is not covered. Each routed PR is handled in its own job.
For each eligible PR, routing does the following:
- Picks the pool using
routes, falling back todefault_pool. For runs triggered by Label PR Intent, that run's intent labels replace the PR's current intent labels, so stale labels can't pick the wrong pool. - Checks whether the pool is already covered. A pool is covered when one of its reviewers or backups, or a core maintainer, is already requested or has already reviewed the PR. If the pool isn't covered, routing requests one individual from the pool, excluding the PR author, reviewers already requested, bots, and anyone listed in
unavailable. A pending request for someone inunavailabledoesn't count as coverage, but a review they already submitted does. It prefers the reviewer with the fewest open review requests across all open PRs. Ties rotate by PR number. - If the pool has no eligible individual, routing tries the pool's
backuplist, then the escalation pool's reviewers, and finally requests the pool team. - Adds a
review-due:YYYY-MM-DDlabel. The date is the routing day plusfirst_review_business_days, in UTC. The time the label was added marks the start of the SLA cycle. - If
needs-reviewertriggered the run, routing always requests a new reviewer: someone who is not already requested and has not already reviewed the PR. It removesneeds-reviewer,review-overdue, andreview-escalated, then removes and re-adds the due label to restart the SLA. If no new reviewer or team can be requested, routing changes nothing, soneeds-reviewerand the current due date stay, and the hourly sweep tries again.
A PR that is already routed is checked again whenever a new commit changes its intent labels. If the new target pool isn't covered, for example because a skills PR now also touches a workflow, routing requests a reviewer from that pool. The existing due date stays the same.
Routing skips drafts, closed PRs, and unrouted PRs that already have a human review, unless needs-reviewer is present. Draft PRs are routed when they are marked ready for review.
Failures and concurrency
- Routing requests reviewers before it changes any labels. If GitHub rejects the request, for example because a team is missing or the token lacks access, routing leaves the labels alone and fails the job. Nothing is marked as routed, so the next run or sweep tries again.
- Each workflow first runs a read-only
planjob that picks the target PRs. It then handles each PR in a job that uses thereview-routing-pr-<number>concurrency group. Review Routing and Review Escalation share that group, so their read, plan, and apply steps never overlap for the same PR. Within one run, the per-PR jobs run one at a time, and each job re-reads reviewer load before it picks someone. - Load balancing across separate runs is best-effort. Two runs that route different PRs at the same moment can read the same load and pick the same reviewer. A repository-wide lock would avoid this, but GitHub cancels pending runs that wait on the same concurrency group, so routing requests could be lost. The next routing run sees the updated load.
- A GitHub Actions matrix can hold at most 256 jobs. If a sweep finds more PRs than that, it handles the first 256, logs a warning, and leaves the rest for the next scheduled run. Planning is idempotent, so no PR is skipped permanently.
SLA and escalation
The Review Escalation workflow (.github/workflows/review-escalation.yml) runs at 14:00 UTC on weekdays and can also be dispatched manually. It checks every routed PR, meaning every PR with a review-due: label.
| Condition | Action |
|---|---|
| A human review arrives after the due label was added | Removes review-due:*, review-overdue, and review-escalated |
| No review by the end of the due date | Requests a backup from the pool's backup list, then from the escalation pool. If no individual is available, it requests the pool team and then the escalation team, unless they are already requested. Adds review-overdue and posts a comment that says who, if anyone, was requested. |
No review escalation_business_days after routing |
Requests the escalation pool team and one core maintainer; adds review-escalated; posts a comment |
Business days are Monday through Friday in UTC, minus sla.holidays. A PR routed on Monday is due Wednesday, becomes overdue Thursday, and escalates Friday, four business days after routing. A PR routed on Wednesday is due Friday, becomes overdue Monday, and escalates Tuesday. Reviews from the author or from bots, including Copilot code review, don't count as a first review. Only reviews submitted after the current due label was added count. Each action happens once per SLA cycle because the labels record the state. If a reviewer request fails, the labels and comment are skipped so the next run retries.
The workflow also deletes review-due:* labels whose dates are more than 14 days in the past and that are no longer on any open PR.
Labels
| Label | Meaning |
|---|---|
needs-reviewer |
Asks routing to (re)assign a reviewer. Routing removes it after it runs. |
review-due:YYYY-MM-DD |
Date by which the first review is expected. |
review-overdue |
The first review is late, and a backup reviewer was requested. |
review-escalated |
The review was escalated to the core-maintainer pool. |
The Setup Repository Labels workflow creates needs-reviewer, review-overdue, and review-escalated. Routing creates each review-due: label when it first uses it.
Security model
- Workflows triggered by pull requests (Label PR Intent and Review Routing Request) run with read-only permissions and only upload a small JSON artifact.
- Review Routing runs from
workflow_run,workflow_dispatch, orschedule. It checks out only the default branch, validates the artifact against the triggering run, and never checks out or runs PR code. This is the same approach aslabel-pr-intent-writer.yml. - In both Review Routing and Review Escalation, the
planjob has read-only permissions. Only the per-PR jobs can write to issues and pull requests. - All reviewer logic lives in
eng/review-routing.mjsand is covered byeng/review-routing.test.mjs.
Enabling routing
The routing workflows ship with dry_run: true and empty reviewer lists. To turn them on:
- Create every team referenced in
CODEOWNERSand in thepoolsof.github/review-routing.yml, each with at least three members, and give each team access to the repository. - Add the team members to the matching
reviewersandbackuplists in.github/review-routing.yml. - Run Setup Repository Labels.
- Set
dry_run: false. - Once the teams and coverage are in place, enable Require review from Code Owners in the
mainruleset.