Skip to content

Ruleset Identity Resolution

GitHub Rulesets identify bypass actors and status check providers by numeric IDs internally. gh-infra abstracts this away — you write human-readable names in YAML, and gh-infra resolves them to numeric IDs when calling the GitHub API.

Each bypass actor in a ruleset is one of five types. You specify the type by field name:

bypass_actors:
- role: admin # Built-in repository role
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

Exactly one type field must be specified per entry. bypass_mode is required for all except org-admin.

flowchart TB
    subgraph resolve["YAML → GitHub API"]
        direction TB
        R1["role: admin"] -->|"hardcoded mapping"| R1out["actor_id: 5<br/>actor_type: RepositoryRole"]
        R2["team: maintainers"] -->|"GET /orgs/{org}/teams/{slug}"| R2out["actor_id: N<br/>actor_type: Team"]
        R3["app: github-actions"] -->|"GET /apps/{slug}"| R3out["actor_id: 15368<br/>actor_type: Integration"]
        R4["org-admin: true"] -->|"fixed value"| R4out["actor_id: 1<br/>actor_type: OrganizationAdmin"]
        R5["custom-role: reviewer"] -->|"GET /orgs/{org}/custom-repository-roles"| R5out["actor_id: N<br/>actor_type: RepositoryRole"]
    end

Maps to a hardcoded, stable numeric ID. No API call needed.

NameAPI actor_idDescription
admin5Full repository admin access
write4Push access, manage issues/PRs
maintain2Manage repo settings without admin

These IDs are not officially documented by GitHub but are stable across GitHub.com, GHES, and all major IaC providers.

Resolved via GET /orgs/{org}/teams/{slug}.id.

  • Requires read:org scope (included in default gh auth scopes)
  • Only available for organization-owned repositories
  • Works for nested (child) teams

Resolved via GET /apps/{slug}.id. This is a public, unauthenticated endpoint.

The resolved value is the App ID (globally unique to the app), not the Installation ID. For example, github-actions resolves to App ID 15368.

No resolution needed. GitHub ignores the actor_id for this type — the API accepts any value. gh-infra sends 1 by convention.

custom-role — Enterprise Cloud Custom Role

Section titled “custom-role — Enterprise Cloud Custom Role”

Resolved via GET /orgs/{org}/custom-repository-roles → matched by name → .id.

Only available on GitHub Enterprise Cloud organizations with custom repository roles configured.

The app field in required_status_checks identifies which GitHub App must provide the check:

rules:
required_status_checks:
contexts:
- context: "CI Gate"
app: github-actions # Only GitHub Actions can satisfy this check
- context: "lint" # Any provider accepted (app omitted)

Forward (YAML → API): Resolved via GET /apps/{slug}.id, same as bypass actor apps. The resolved App ID is sent as integration_id in the API payload.

Reverse (API → YAML on import): Resolved via GET /repos/{owner}/{repo}/commits/HEAD/check-runs. Each check run includes the providing App’s ID and slug, so gh-infra discovers the slug by matching the integration_id against check run results.

app fieldAPI behavior
Specified (e.g., app: github-actions)Only the named App can satisfy the check
OmittedAny provider matching the context name satisfies the check

For most users, omitting app is sufficient. Specify it only when you need to ensure a specific App provides the check — for example, to prevent a third-party app from satisfying a check by using the same name.

Within a single plan, apply, or import execution, resolved IDs are cached. If multiple rulesets reference the same team or app slug, only one API call is made.

If name resolution fails (e.g., team slug not found, app slug invalid), the plan or apply command stops with an error before making any changes. This ensures you never accidentally apply a ruleset with incorrect actor references.

When gh infra import exports a repository’s rulesets, numeric IDs from the API are converted back to human-readable names:

API responseExported YAML
actor_id: 5, actor_type: RepositoryRolerole: admin
actor_id: N, actor_type: Teamteam: {slug} (resolved via org teams API)
actor_id: N, actor_type: Integrationapp: {slug} (resolved via check-runs API)
actor_type: OrganizationAdminorg-admin: true
integration_id: N in status checksapp: {slug} (resolved via check-runs API)

If reverse resolution fails, the export falls back to app: "id:N" format. This commonly happens with private GitHub Apps that are not publicly resolvable via GET /apps/{slug} and have no check-runs on the repository.

The id:N format is fully functional — plan and apply accept it as-is and resolve the numeric ID directly, bypassing slug lookup. No manual correction is required, though you may replace it with the actual slug for readability if you know it.