Skip to content

ADR-0018: Branch protection is advisory, not enforced

ADR-0018: Branch protection is advisory, not enforced

  • Status: Accepted
  • Date: 2026-07-30
  • Amends: ADR-0015
  • Supersedes:
  • Superseded by:

Context

ADR-0015 gives agents full autonomy on a branch and none to merge, and lists branch protection on main as the mechanism that enforces it. That consequence turned out to be unachievable as written.

On GitHub’s free tier, both protection mechanisms are unavailable for private repositories. Verified empirically on 30 July 2026 against psifive/tabletop-tracker:

MechanismResult
PUT /repos/{owner}/{repo}/branches/{branch}/protection403 — Upgrade to GitHub Pro or make this repository public
POST /repos/{owner}/{repo}/rulesets403 — Upgrade to GitHub Pro or make this repository public

Three ways out: make the repository public, pay $4/month for GitHub Pro, or accept that the rule is unenforced.

Decision

Keep the repository private and accept advisory enforcement.

  • The rule in ADR-0015 stands unchanged: agents never push to main and never merge.
  • A committed pre-push hook at .githooks/pre-push blocks pushes to main, enabled with git config core.hooksPath .githooks.
  • The hook is a mitigation, not a control. It is client-side, bypassable with --no-verify, and inert until core.hooksPath is set.

Rationale

The repository is private because the roadmap and build plan are not something to publish pre-launch, and that preference is worth more than the enforcement gap it creates. $4/month is small in absolute terms, but the £0 constraint in docs/architecture.md §1 is a deliberate discipline and the Apple fee is meant to remain the only compulsory recurring spend.

The gap is narrower than it looks. Agents in this project act on AGENTS.md and docs/standards/the-loop.md, both of which state the rule prominently; the realistic failure is an accidental push, not a deliberate one, and the hook catches accidents. What is genuinely lost is the guarantee — and the honest thing is to record that rather than let the delivery system read as if main were protected when it is not.

Consequences

  • main is not protected. A push to it will succeed if the hook is unconfigured or bypassed. Anyone reading docs/standards/ci-and-gates.md should not assume otherwise.
  • WI-003 can no longer verify branch protection. Its DoD is amended to verify the hook instead, which is a weaker check for a weaker guarantee.
  • git config core.hooksPath .githooks becomes a setup step in docs/standards/environment.md and part of WI-001. A fresh clone without it has no protection at all — this is the mitigation’s main weakness.
  • CI still gates merges into main once WI-003 lands. What is unenforced is the bypass — pushing straight past the PR — not the gate itself.
  • A push to main is a process failure and should be treated as one, reviewed like any other incident, regardless of whether the hook caught it.

Revisiting

This decision should be revisited on any of these triggers, and the answer may well change:

  • A second contributor joins — convention across two people is materially weaker than across one.
  • The repository goes public at launch, at which point protection is free and this ADR is superseded.
  • An accidental push to main actually happens, which would be evidence the mitigation is insufficient.

Alternatives considered

Make the repository public. Free, and fully restores enforcement. Rejected: the roadmap and build plan should not be world-readable before launch. Reconsider at launch.

GitHub Pro at $4/month. Restores enforcement while staying private, and is under half the Apple fee already accepted. Rejected for now on the £0 principle rather than the amount — but this is the option to take if the repository gains a second contributor while still private.