Rulesets
Configure GitHub repository rulesets — the modern replacement for classic branch protection rules, with richer controls and enforcement modes.
Example
Section titled “Example”reconcile: rulesets: additive
spec: rulesets: - name: protect-main target: branch enforcement: active bypass_actors: - role: admin bypass_mode: always conditions: ref_name: include: - "refs/heads/main" - "refs/heads/release/*" exclude: - "refs/heads/sandbox/*" rules: pull_request: required_approving_review_count: 1 dismiss_stale_reviews_on_push: true require_code_owner_review: false require_last_push_approval: false required_review_thread_resolution: false required_status_checks: strict_required_status_checks_policy: true contexts: - context: "ci/test" app: github-actions non_fast_forward: true update: false deletion: true creation: false required_linear_history: false required_signatures: false
- name: protect-tags target: tag enforcement: evaluate conditions: ref_name: include: ["refs/tags/v*"] rules: deletion: true non_fast_forward: trueReconcile Mode
Section titled “Reconcile Mode”Rulesets are additive by default. gh-infra creates and updates rulesets listed in spec.rulesets, but it does not delete rulesets that exist on GitHub and are missing from YAML.
Set reconcile.rulesets: authoritative to make GitHub rulesets match YAML exactly:
reconcile: rulesets: authoritativespec: rulesets: - name: protect-main target: branch rules: non_fast_forward: trueWith authoritative, any repository ruleset not declared in spec.rulesets is planned for deletion. To delete all repository rulesets:
reconcile: rulesets: authoritativespec: rulesets: []rulesets: null and rulesets: are invalid. Use [] for an explicitly empty managed collection.
Top-level Fields
Section titled “Top-level Fields”| Field | Type | Default | Description |
|---|---|---|---|
name |
string | required | Ruleset name (must be unique within the repository) |
target |
string | branch on create |
What the ruleset applies to: branch or tag |
enforcement |
string | active on create |
active (enforced), evaluate (dry-run, logged but not enforced), or disabled |
bypass_actors |
list | [] on create |
Actors who can bypass this ruleset. [] removes all actors from an existing ruleset |
conditions |
object | — | Which branches or tags the ruleset applies to |
rules |
object | required | The rules to enforce |
The defaults apply when a ruleset is created. See Updating an existing ruleset for what happens to settings you leave out afterwards.
Updating an existing ruleset
Section titled “Updating an existing ruleset”GitHub replaces the whole ruleset on update, so gh-infra reads the current ruleset and changes only what the manifest sets. Everything the manifest leaves out keeps its current value:
target,enforcement,bypass_actors, andconditions- rules the manifest does not mention, including rule types gh-infra does not model (such as
merge_queueorcommit_message_pattern) - parameters the manifest does not set, including ones gh-infra does not model (such as
pull_request.allowed_merge_methods)
plan compares only what the manifest sets, which is what apply changes.
To remove something, say so in the manifest:
bypass_actors: [] # remove all bypass actorsrules: deletion: false # toggle rules and update: false removes them pull_request: false # remove the pull_request rule required_status_checks: falseBecause settings you leave out are kept, a change made on GitHub to one of them stays and does not show up in plan. For example, a ruleset switched to disabled in the GitHub UI stays disabled if the manifest does not set enforcement. Set enforcement (and anything else you want enforced) explicitly. reconcile.rulesets: authoritative controls which rulesets exist, not the rules inside a ruleset you declare.
Bypass Actors
Section titled “Bypass Actors”Each entry allows a specific actor to bypass the ruleset. Specify the actor type by field name:
bypass_actors: - role: admin # Built-in role: admin, write, or maintain bypass_mode: always - team: maintainers # Organization team by slug bypass_mode: pull_request - app: github-actions # GitHub App by slug bypass_mode: always - org-admin: true # Organization administrators - custom-role: reviewer # Enterprise Cloud custom role by name bypass_mode: pull_request| Field | Description |
|---|---|
role |
Built-in repository role: admin, write, or maintain |
team |
Organization team slug (resolved via API) |
app |
GitHub App slug (resolved via API). Private apps that cannot be resolved are exported as id:N by import, which is accepted as-is by plan and apply |
org-admin |
Set to true for organization administrators |
custom-role |
Enterprise Cloud custom role name (resolved via API) |
bypass_mode |
always (pushes and PRs) or pull_request (PRs only) |
Teams and custom roles are looked up in the organization that owns the repository, so one directory of manifests can manage repositories of several organizations.
Exactly one actor type field must be specified per entry. Names are automatically resolved to numeric IDs — see Ruleset Identity Resolution for details.
Conditions
Section titled “Conditions”Specify which branches or tags the ruleset targets using fnmatch-style patterns:
conditions: ref_name: include: - "refs/heads/main" - "refs/heads/release/*" exclude: - "refs/heads/sandbox/*"Special values: ~DEFAULT_BRANCH matches the repository’s default branch, ~ALL matches all branches/tags.
pull_request
Section titled “pull_request”Require pull request reviews before merging:
| Field | Type | Default | Description |
|---|---|---|---|
required_approving_review_count |
int | 0 |
Number of required approving reviews (0–10) |
dismiss_stale_reviews_on_push |
bool | false |
Dismiss approvals when new commits are pushed |
require_code_owner_review |
bool | false |
Require review from code owners |
require_last_push_approval |
bool | false |
Last pusher cannot self-approve |
required_review_thread_resolution |
bool | false |
All review threads must be resolved |
GitHub requires every parameter of this rule. When the rule is created, the ones you leave out are sent with the defaults above. On an existing rule, the ones you leave out keep their current values. Set pull_request: false to remove the rule.
required_status_checks
Section titled “required_status_checks”Require specific CI checks to pass:
rules: required_status_checks: strict_required_status_checks_policy: true contexts: - context: "ci/test" app: github-actions - context: "ci/lint"| Field | Type | Description |
|---|---|---|
strict_required_status_checks_policy |
bool | Require branch to be up to date before merging |
contexts[].context |
string | Name of the required status check |
contexts[].app |
string | (optional) GitHub App slug that must provide this check. Omit to accept any provider |
GitHub requires both contexts and strict_required_status_checks_policy. When the rule is created, the ones you leave out are sent as no checks and false. On an existing rule, leaving out either keeps the current value. Set required_status_checks: false to remove the rule.
Toggle Rules
Section titled “Toggle Rules”Simple on/off rules. Set to true to enable, false to remove; leaving one out keeps the current state:
| Field | Description |
|---|---|
non_fast_forward |
Block force pushes to matching refs |
deletion |
Block deletion of matching refs |
creation |
Block creation of matching refs |
required_linear_history |
Require linear commit history (no merge commits) |
required_signatures |
Require signed commits |
Update Rule
Section titled “Update Rule”Set update to true to block updates to matching refs:
update: trueFor branch rulesets, use object form to control whether fork branches may pull changes from their upstream repository:
update: allows_fetch_and_merge: trueallows_fetch_and_merge maps to the REST API
update_allows_fetch_and_merge parameter and is not supported for tag rulesets.
GitHub may omit this parameter from API responses (for example, on user-owned
repositories). When it does, plan cannot detect drift for this field.
With the bool form (update: true), gh-infra does not manage
allows_fetch_and_merge: it neither compares nor changes the parameter, and an
existing value is kept.
Rulesets vs Classic Branch Protection
Section titled “Rulesets vs Classic Branch Protection”| Feature | Classic branch_protection |
rulesets |
|---|---|---|
| Multiple rules per branch | No (one rule per pattern) | Yes (rules stack) |
| Enforcement modes | On/off only | Active, evaluate (dry-run), disabled |
| Bypass control | Admin override only | Granular per role/team/app |
| Tag protection | No | Yes (target: tag) |
| Audit trail | No | Built-in version history |
| Merge queue | Separate config | Integrated as a rule type (Phase 2) |
| Metadata rules | No | Commit message patterns, etc. (Phase 2) |