Skip to content

fix(bootstrap): stop applying classic branch protection - #349

Open
jdfalk wants to merge 1 commit into
mainfrom
chore/drop-classic-branch-protection
Open

fix(bootstrap): stop applying classic branch protection#349
jdfalk wants to merge 1 commit into
mainfrom
chore/drop-classic-branch-protection

Conversation

@jdfalk

@jdfalk jdfalk commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Why

falkcorp manages branch rules with org-wide rulesets. The bootstrap-repo skill was also installing per-repo classic branch protection, which is actively harmful:

  1. Classic protection has no bypass_actors list. Rulesets can exempt automation; classic protection can exempt no one. It blocked the Sync from Template bot from pushing to main in 15 gha-* repos for ~4 months:

    remote: error: GH006: Protected branch update failed for refs/heads/main.
    remote: - Changes must be made through a pull request.
    

    Adding bypass actors to the org rulesets did nothing — classic protection does not consult them. Deleting it fixed the sync on the next run.

  2. The required contexts could never be satisfied. discover_status_checks.py collected job IDs, but GitHub reports checks by display name:

    required context (job id) actually reported
    test-with-config Test With Valid Config
    validate Validate Action

    Those contexts sat Expected forever, so every PR was blocked — 34 Dependabot PRs unmergeable, some for 6+ months.

  3. Conditional jobs became required checks. set-auto-merge is gated on if: contains(labels, 'auto-merge'). Unlabelled PRs skip it, and a skipped required check never satisfies protection. The auto-merge label did not exist in any repo.

What changed

  • Deleted apply_branch_protection.sh and discover_status_checks.py
  • Dropped --skip-protection from bootstrap_repo.sh
  • Inverted verify_bootstrap.sh: the presence of classic protection is now reported as drift, with the removal command
  • Removed the dead --repo-path arg from verify_bootstrap.sh and both call sites (it existed only to feed discover_status_checks.py)
  • Rewrote references/branch-protection.md as a do-not-do-this rationale

Latent bug also fixed

The old check did PROTECTION=$(gh api ... || echo "") then tested -z. gh api prints HTTP error bodies to stdout, so a 404 still yields a non-empty string — the "no protection" case was undetectable. Now tests the exit status.

Verification

  • bash -n + shellcheck -S warning clean on all scripts
  • Verifier tested both branches against real repos, after shfmt reformatting:
    • repo without protection → ✓ No classic branch protection
    • protection temporarily re-added → ✗ classic branch protection is present, then removed again
  • No dangling references to the deleted scripts remain

Rollout already applied

Classic protection removed from all 45 falkcorp repos (0 remain); configs backed up. The template-sync bot pushed successfully for the first time since 2026-04-25.

falkcorp manages branch rules with org-wide rulesets. The bootstrap-repo skill
was also installing per-repo classic protection, which is actively harmful:

1. Classic protection has no bypass_actors list. Org rulesets can exempt
   automation; classic protection cannot exempt anyone. It blocked the
   Sync from Template bot from pushing to main in 15 gha-* repos for ~4
   months with GH006 "Changes must be made through a pull request".
   Adding bypass actors to the org rulesets did nothing, because classic
   protection does not consult them.

2. discover_status_checks.py collected job IDs, but GitHub reports status
   checks by display name. Required contexts like test-with-config never
   matched the reported Test With Valid Config, so they sat "Expected"
   forever and every PR was blocked. 34 Dependabot PRs were unmergeable.

3. Autodiscovery swept up set-auto-merge, whose job is gated on
   if: contains(labels, 'auto-merge'). Unlabelled PRs skip it, and a
   skipped required check never satisfies protection.

Deleted apply_branch_protection.sh and discover_status_checks.py, dropped
--skip-protection, and inverted verify_bootstrap.sh so the presence of
classic protection is reported as drift.

Also fixes a latent bug in that check: it tested gh api's output instead of
its exit status, but gh api prints HTTP error bodies to stdout, so a 404
yielded a non-empty string and the absent case was undetectable. Verified
both branches against real repos, with and without protection.

Removed the dead --repo-path argument from verify_bootstrap.sh and both of
its call sites; it existed only to feed discover_status_checks.py.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UsssaXSKwYAeEcV218SsnS
@github-actions github-actions Bot added size/L type:documentation Improvements or additions to documentation tech:python Python programming language tech:shell Shell scripting (bash, sh) labels Aug 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/L tech:python Python programming language tech:shell Shell scripting (bash, sh) type:documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant