|
| 1 | +# Code Ownership |
| 2 | + |
| 3 | +This repository uses [Codeowners Plus](https://github.com/multimediallc/codeowners-plus) to enforce code ownership and review requirements. This replaces GitHub's native CODEOWNERS with more fine-grained control — specifically, the ability to require approval from **multiple teams** (AND rules) before a PR can merge. |
| 4 | + |
| 5 | +## How It Works |
| 6 | + |
| 7 | +### Overview |
| 8 | + |
| 9 | +When a PR is opened, updated, or reviewed, the Codeowners Plus GitHub Action runs. It reads `.codeowners` and `codeowners.toml` from the **base branch** (not the PR), evaluates the ownership rules, and: |
| 10 | + |
| 11 | +- Posts a PR comment listing which teams need to approve |
| 12 | +- Requests reviews from those teams |
| 13 | +- Sets a **required status check** ("Run Codeowners Plus") that passes only when all ownership rules are satisfied |
| 14 | + |
| 15 | +The native GitHub `CODEOWNERS` file is not involved in enforcement. |
| 16 | + |
| 17 | +### Key Difference from Native CODEOWNERS |
| 18 | + |
| 19 | +| Feature | Native GitHub CODEOWNERS | Codeowners Plus | |
| 20 | +| ------------------------ | --------------------------------- | ------------------------------------------------------------------------------- | |
| 21 | +| Multiple teams on a path | **OR** — any one team can approve | **AND** via `&` prefix — all listed teams must approve | |
| 22 | +| Path matching for `*.js` | Matches anywhere in repo | Matches only in the `.codeowners` file's directory; use `**/*.js` for recursive | |
| 23 | +| Per-directory config | Single file only | `.codeowners` file in any directory (rules are relative to that directory) | |
| 24 | +| Stale review dismissal | All-or-nothing | Smart — only dismisses when reviewer's owned files change | |
| 25 | +| Optional reviewers | Not supported | `?` prefix — CC without blocking | |
| 26 | + |
| 27 | +## Configuration Files |
| 28 | + |
| 29 | +### `.codeowners` — Ownership Rules |
| 30 | + |
| 31 | +Located at the repo root. Defines who owns what using path patterns and team handles. See the comments in the file itself for syntax details and a template for adding new product teams. |
| 32 | + |
| 33 | +### `codeowners.toml` — Advanced Configuration |
| 34 | + |
| 35 | +Located at the repo root. Controls enforcement behavior, ignored paths, and admin bypass. |
| 36 | + |
| 37 | +Key settings: |
| 38 | + |
| 39 | +| Setting | Purpose | |
| 40 | +| -------------------------- | -------------------------------------------------------------------------- | |
| 41 | +| `ignore` | Directories excluded from ownership checks (e.g. `.changeset`, `fixtures`) | |
| 42 | +| `detailed_reviewers` | Show per-file owner breakdown in PR comments | |
| 43 | +| `suppress_unowned_warning` | Don't warn about files with no owner | |
| 44 | +| `enforcement.fail_check` | When `true`, the GHA check fails if rules aren't satisfied | |
| 45 | +| `admin_bypass.enabled` | Allow admins to bypass by approving with "Codeowners Bypass" text | |
| 46 | + |
| 47 | +### `CODEOWNERS` — Native GitHub File |
| 48 | + |
| 49 | +The native GitHub `CODEOWNERS` file is kept for reference but is **not the enforcement mechanism**. Enforcement is handled by the Codeowners Plus required status check. All ownership logic lives in `.codeowners`. |
| 50 | + |
| 51 | +### `.github/workflows/codeowners.yml` — GitHub Actions Workflow |
| 52 | + |
| 53 | +A single workflow handles all events: |
| 54 | + |
| 55 | +- `pull_request_target` — PR opened, updated, marked ready, labeled |
| 56 | +- `pull_request_review` — review submitted or dismissed |
| 57 | + |
| 58 | +Using `pull_request_target` (not `pull_request`) ensures the workflow has access to secrets for **fork PRs**. The checkout is always the base branch, so PR authors cannot modify ownership rules. |
| 59 | + |
| 60 | +## Common Scenarios |
| 61 | + |
| 62 | +### PR touches only wrangler-team-owned code |
| 63 | + |
| 64 | +Example: changes to `packages/create-cloudflare/` or `packages/vite-plugin-cloudflare/`. |
| 65 | + |
| 66 | +Only `@cloudflare/wrangler` approval is required. |
| 67 | + |
| 68 | +### PR touches product-team-owned code |
| 69 | + |
| 70 | +Example: changes to `packages/wrangler/src/d1/`. |
| 71 | + |
| 72 | +**Both** `@cloudflare/wrangler` AND `@cloudflare/d1` must approve. Codeowners Plus will post a comment listing who still needs to approve and request reviews from both teams. |
| 73 | + |
| 74 | +### PR touches multiple product areas |
| 75 | + |
| 76 | +Example: changes to both `packages/wrangler/src/d1/` and `packages/wrangler/src/kv/`. |
| 77 | + |
| 78 | +All three teams must approve: `@cloudflare/wrangler` + `@cloudflare/d1` + `@cloudflare/workers-kv`. |
| 79 | + |
| 80 | +### PR touches ignored paths only |
| 81 | + |
| 82 | +Example: changes only in `.changeset/` or `fixtures/`. |
| 83 | + |
| 84 | +No ownership checks apply. The codeowners-plus check passes automatically. |
| 85 | + |
| 86 | +### Draft PRs |
| 87 | + |
| 88 | +The workflow runs in **quiet mode** for draft PRs: |
| 89 | + |
| 90 | +- No PR comments posted |
| 91 | +- No review requests sent |
| 92 | +- The status check still runs for visibility |
| 93 | + |
| 94 | +### Emergency bypass |
| 95 | + |
| 96 | +Repository admins can bypass all requirements by submitting an **approval review** with the text "Codeowners Bypass" (case-insensitive). This creates an audit trail. |
| 97 | + |
| 98 | +### Fork PRs |
| 99 | + |
| 100 | +Fork PRs are fully supported. The workflow uses `pull_request_target` to run in the base repo context with access to secrets. The base branch is checked out (so ownership rules come from the protected branch), and the PR head is fetched as git objects only for diff computation. No fork code is executed. |
| 101 | + |
| 102 | +## Adding a New Product Team |
| 103 | + |
| 104 | +To add ownership for a new product team, add AND rules to `.codeowners`: |
| 105 | + |
| 106 | +```bash |
| 107 | +# Product: <Name> (AND: requires wrangler + <team>) |
| 108 | +& packages/wrangler/src/<feature>/** @cloudflare/<team> |
| 109 | +& packages/wrangler/src/__tests__/<feature>/** @cloudflare/<team> |
| 110 | +& packages/miniflare/src/plugins/<feature>/** @cloudflare/<team> |
| 111 | +& packages/miniflare/src/workers/<feature>/** @cloudflare/<team> |
| 112 | +& packages/miniflare/test/plugins/<feature>/** @cloudflare/<team> |
| 113 | +``` |
| 114 | + |
| 115 | +For example, to add R2 ownership: |
| 116 | + |
| 117 | +```bash |
| 118 | +# Product: R2 (AND: requires wrangler + r2) |
| 119 | +& packages/wrangler/src/r2/** @cloudflare/r2 |
| 120 | +& packages/wrangler/src/__tests__/r2/** @cloudflare/r2 |
| 121 | +& packages/miniflare/src/plugins/r2/** @cloudflare/r2 |
| 122 | +& packages/miniflare/src/workers/r2/** @cloudflare/r2 |
| 123 | +& packages/miniflare/test/plugins/r2/** @cloudflare/r2 |
| 124 | +``` |
| 125 | + |
| 126 | +**Teams ready to add** (have source paths but no ownership entries yet): |
| 127 | +R2, Queues, AI, Hyperdrive, Vectorize, Pipelines, SSL/Secrets Store, WVPC. |
| 128 | + |
| 129 | +## Stale Review Handling |
| 130 | + |
| 131 | +Codeowners Plus uses **smart dismissal**: when new commits are pushed to a PR, it only dismisses an approval if the files owned by that reviewer were changed. This avoids the frustration of GitHub's all-or-nothing stale review dismissal. |
| 132 | + |
| 133 | +For this to work, the branch protection setting **"Dismiss stale pull request approvals when new commits are pushed"** must be **disabled**. Codeowners Plus handles dismissal itself. |
| 134 | + |
| 135 | +## References |
| 136 | + |
| 137 | +- [Codeowners Plus documentation](https://github.com/multimediallc/codeowners-plus) |
| 138 | +- [Codeowners Plus action on GitHub Marketplace](https://github.com/marketplace/actions/codeowners-plus) |
| 139 | +- [GitHub branch protection docs](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches) |
0 commit comments