Skip to main content

repo-policy

Lightweight, declarative repository governance for GitHub. Define expected branch protection and ruleset configuration in YAML; audit, preview, and apply it locally or in CI.

Not a Terraform replacement — no state file, no backend. repo-policy is safe to adopt incrementally on a live repository: by default it only ever touches branches you declare, and never deletes anything you didn't ask it to manage.

Install

pip install repo-policy

Quick start

# policy.yml
version: 1

branches:
  main:
    pull_requests:
      required: true
      approvals: 2
      code_owner_review: true
    status_checks:
      required: [build, test]
    signed_commits: true
    linear_history: true
    allow_force_push: false
    allow_deletion: false
repo-policy validate
repo-policy audit --repo acme/widgets
repo-policy plan --repo acme/widgets
repo-policy apply --repo acme/widgets

CLI Reference

Command Flags Behavior Exit codes
--version Prints repo_policy.__version__ and exits; no network calls 0
validate --config (default policy.yml) Parses and schema-validates the policy file only; no network calls 0 valid, 2 invalid
audit --config, --repo, --token Read-only compliance check 0 compliant, 1 drift found, 2 invalid config, 3 API/auth error
plan --config, --repo, --token Same diff engine as audit, renders a human-readable +/-/~/✓ preview same as audit
apply --config, --repo, --token Executes only the changes plan would show, then re-verifies live state from scratch before reporting success 0 mutations converged (incl. no-op), 1 mutations succeeded but a fresh post-apply check still finds drift, 2 invalid config or setup failure, 3 API/auth error or partial-application failure

Token resolution order: --tokenGITHUB_TOKENGH_TOKEN. Repo resolution order: --repo owner/name$GITHUB_REPOSITORY → the local git origin remote.

See ARCHITECTURE.md's "Apply Outcomes and Exit Codes" for the full preflight / mutate / verify breakdown behind apply's row above.

Schema & Validation

repo-policy fails closed on ambiguous or malformed policy.yml input rather than guessing what you meant (src/repo_policy/models.py, src/repo_policy/config.py):

  • Unknown fields are rejected, not silently ignored — every schema model uses Pydantic's extra="forbid", so a typo like strcit: or secret_scaning: is a validation error at validate time, not a quietly-ignored no-op.
  • No implicit type coercion — every model also sets strict=True: a quoted boolean (signed_commits: "true"), yes/no/on/off, or a numeric string in place of a real integer is rejected rather than silently coerced.
  • Duplicate YAML keys are rejected — a custom YAML loader fails a policy file with two strict: keys, two entries for the same branch name, etc., instead of PyYAML's default silent last-key-wins behavior.
  • pull_requests.approvals must be 06 — anything outside that range is rejected before any API call is made.

Supported fields:

Section Field Type / range
top level version must be 1
top level strict bool (default false)
top level branches {branch name: branch policy}
top level repo_settings optional, see below
per branch enforcement branch_protection (default) | ruleset
per branch strict bool, overrides the top-level default for this branch
per branch pull_requests.required bool (default true)
per branch pull_requests.approvals int, 06 (default 1)
per branch pull_requests.code_owner_review bool
per branch pull_requests.dismiss_stale_reviews bool
per branch pull_requests.require_last_push_approval bool
per branch status_checks.required list[str], no blank or duplicate entries
per branch signed_commits, linear_history, allow_force_push, allow_deletion bool
per branch enforce_admins, required_conversation_resolution, lock_branch, allow_fork_syncing, clear_restrictions boolno GitHub Rulesets equivalent; rejected under enforcement: ruleset unless left at their permissive (no-op) value
repo_settings delete_branch_on_merge, allow_update_branch, vulnerability_alerts, automated_security_fixes, private_vulnerability_reporting, secret_scanning, secret_scanning_push_protection bool

Two cross-field rules are enforced at validation time, before any API call:

  • allow_fork_syncing: true requires lock_branch: true on the same branch — GitHub silently resets allow_fork_syncing back to false otherwise.
  • automated_security_fixes: true requires vulnerability_alerts: true, and secret_scanning_push_protection: true requires secret_scanning: true — GitHub rejects enabling either one before its prerequisite.

repo-policy validate checks all of the above against a real policy.yml, entirely offline.

GitHub Action

Pin to an immutable full commit SHA — the same convention this repository's own CI uses for every third-party Action it consumes (see .github/workflows/ci.yml), and the general supply-chain hardening recommendation for any Action, including this one:

- uses: shipsolid/repo-policy@a4d736b7fa8268115f50e61b4398d8d6a7ee7e1a # v0.4.7
  env:
    GITHUB_TOKEN: ${{ secrets.REPO_POLICY_TOKEN }}
  with:
    config: .github/repository-policy.yml
    mode: audit

Every release tag here (v<version>) is an annotated tag object, not a direct pointer to a commit — so git rev-parse v<version> alone returns that tag object's own SHA, not the commit's, and pinning uses: to it would silently defeat the point of pinning to a commit. Peel through the tag to get the actual commit SHA to pin:

git rev-parse v<version>^{commit}

or find it at github.com/shipsolid/repo-policy/tags → click the release tag → the commit it points to. (Once the release-bot signing pipeline in SECURITY.md's "Release Signing" section is live — see its Release Pipeline Setup Checklist for current status — each of these annotated tags will also carry a verifiable SSH signature; that doesn't change which SHA to pin here.)

Convenience alternative — @v0: a floating tag tracking the current major version (the same convention actions/checkout and similar Actions use). Each release force-moves it to nest, unpeeled, directly on top of that release's own annotated tag object (v0v<version> → the release commit) — this nesting is what will let git verify-tag v0 keep verifying transitively against the release-bot's signature after every move, once that signing pipeline is live (see docs/ci-cd.md).

- uses: shipsolid/repo-policy@v0

@v0 is movable, not immutable. What it resolves to changes on every release without any corresponding change to your own workflow file to review — convenient for staying current automatically, but unsuitable anywhere change control requires a pinned, auditable dependency (the same full-SHA-pinning convention this repository's own workflows follow for every third-party Action they consume — see docs/ci-cd.md). Prefer the full-SHA form above unless you have a specific reason to track the moving tag instead.

secrets.GITHUB_TOKEN will not work here, in any workflow, no matter what permissions: you grant it — confirmed against a real repo. GitHub Actions' automatically-generated token has no permission scope covering branch protection or ruleset administration; that's a platform constraint, not something a workflow can opt into. Create a PAT with repo scope (classic) or Administration: Read and write (fine-grained), store it as a repository secret — REPO_POLICY_TOKEN above is just an example name — and reference that secret instead.

Self-governance

This repository governs itself with its own tool: .github/repository-policy.yml declares main's branch protection and repo security settings, and .github/workflows/policy-audit.yml runs repo-policy audit against it on a daily schedule, on every change to the policy file or that workflow, and on demand.

  • The audit workflow is read-only (mode: audit) and needs a POLICY_AUDIT_TOKEN repository secret — a fine-grained PAT scoped to this repository only, with Administration: Read. That secret does not exist yet; creating it is a live-repo setup step for whoever holds admin access.
  • .github/repository-policy.yml documents the intended branch protection for this repository — it has not yet been applied. Until repo-policy apply runs against the live repository (a manual, reviewed step — see SECURITY.md's Threat Model for why apply is never run unattended against a real repo from an untrusted trigger), it does not reflect live GitHub state.
  • If a change to main's required status check ever leaves it unable to produce a passing required result — blocking the very fix that would repair it — see SECURITY.md's "Emergency Recovery" for the documented, auditable bypass procedure.

How it works

Every declared branch is diffed against live GitHub state and reconciled through one of two backends, selected per branch with enforcement: branch_protection | ruleset (default branch_protection). See ARCHITECTURE.md for the full data flow, safety model, and known v1 limitations, and docs/adrs/ for why it's built this way.

Exit codes

This mapping is shared by audit, plan, and apply alike:

Code Meaning
0 Success / compliant / no-op
1 Drift detected (audit/plan), or apply's mutations succeeded but a fresh, independent post-apply check still finds drift or an unavailable declared setting
2 Invalid policy.yml, a setup failure before any API call (missing token, unresolvable --repo, GitHub-client construction failure), or a PolicyResolutionError after a successful read (GitHub's live state was unparseable, or resolving the declared policy against it produced an invalid combination) — this last case can happen even with a fully valid policy.yml
3 GitHub API/auth/transport error, or a partial-application failure (a mutation failed partway through an apply's mutation phase; zero or more earlier resources in that phase may already have succeeded)

More docs

License

MIT

Release files for repo-policy 0.4.10

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for repo-policy 0.4.10
File Size Uploaded
repo_policy-0.4.10.tar.gz 208.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for repo-policy 0.4.10
File Interpreter ABI Platform
repo_policy-0.4.10-py3-none-any.whl Python 3 none any Details

Total release size: 256.6 kB

Release files / repo_policy-0.4.10.tar.gz

Download URL repo_policy-0.4.10.tar.gz
Size 208.9 kB
Tags Source
SHA-256 checksum
How to use checksums
ed910ae29618e2fe2faa7d8f35032306ef402a93a67e4138132075a4c57bdab1
BLAKE2b-256 checksum
How to use checksums
11011058fbb919fd3a730a5d2f5aa63ff9a94b3f0ac63a424e6ad15cf7a80d5b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.

Transparency log

Release files / repo_policy-0.4.10-py3-none-any.whl

Download URL repo_policy-0.4.10-py3-none-any.whl
Size 47.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
814d10adcbd9c9ffe343a9880bf077f390bf0aad8c82bbbeb8142651ce967e30
BLAKE2b-256 checksum
How to use checksums
e4ffe6ec74fb922249cead7d6b8e45746cfb363730af0320d0370cd3a297911a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.

Transparency log

Release history Release notifications | RSS feed

0.5.0

2 release files

This release

0.4.10 This release

2 release files

0.4.9

2 release files

0.4.7

2 release files

0.4.6

2 release files

0.4.5

2 release files

0.4.4

2 release files

0.4.3

2 release files

0.4.2

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.4

2 release files

0.1.3

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page