Solo-Maintainer PR-Merge Ceremony
When the maintainer can merge their own pull request directly, and the ceremony for a branch protection that requires an approval and enforces it for admins.
Canonical reference for self-merging PRs under the project's branch protection. The live protection on
mainrequires 0 approving reviews and leavesenforce_adminsoff, as recorded on the Branch protection page. The maintainer therefore merges their own pull request directly. The temp-toggle ceremony on this page applies only when the protection requires an approval and enforces it for admins.
1. The Deadlock
The live main protection carries:
required_pull_request_reviews.required_approving_review_count: 0enforce_admins: false
Under these settings there is no deadlock. A maintainer pull request merges with gh pr merge <pr-number> --squash --delete-branch once its required status checks are green and its review conversations are resolved.
A deadlock exists only when the protection carries both of these stricter settings:
required_pull_request_reviews.required_approving_review_count: 1enforce_admins: true
These rules are correct for multi-maintainer scenarios (every change reaches main through a peer-reviewed PR; admins are not exempt). They produce a deadlock for solo maintainers: GitHub's policy disallows self-approval on a PR you authored, and enforce_admins=true blocks the gh pr merge --admin override of the review-approval requirement. With enforce_admins: false, --admin bypasses the approval requirement and no toggle is needed, but it bypasses red required checks too, so confirm they are green before using it.
Three resolution paths exist:
| Path | When to use |
|---|---|
Permanently set required_approving_review_count: 0 (live setting) | Adopt only if the repo will remain solo-maintainer indefinitely; reverses the protection's review intent |
| Temp-toggle ceremony | Solo-maintainer self-merges while the protection requires an approval with enforce_admins=true; preserves the protection invariant outside the merge window |
| Provision a CI-bot identity with write access | Adopt only if multi-maintainer onboarding is imminent; bot-approval undermines the human-review intent of the protection |
This repository currently uses the permanent zero-approval path. The temp-toggle ceremony is the canonical path whenever the protection requires an approval and enforces it for admins.
2. The Temp-Toggle Ceremony
The ceremony's invariant: the protection state at the start of the ceremony equals the protection state at the end. The relaxation lives only inside the merge window.
Manual form:
# 1. Open the PR, push branch, wait for CI green.
gh pr create --base main --head <feature-branch> --title "..." --body "..."
gh pr checks <pr-number> # poll until all green
# 2. Relax approver-count to 0.
gh api repos/<owner>/<repo>/branches/main/protection/required_pull_request_reviews \
-X PATCH -F required_approving_review_count=0
# 3. Squash-merge with admin override (admin override now succeeds because the
# review-approval requirement is the only block enforce_admins refuses).
gh pr merge <pr-number> --squash --delete-branch --admin
# 4. Restore approver-count to 1.
gh api repos/<owner>/<repo>/branches/main/protection/required_pull_request_reviews \
-X PATCH -F required_approving_review_count=1Wrapped form: see scripts/dev/admin_merge.py for the atomic Python wrapper that executes steps 2 → 3 → 4 in a single non-interactive sequence with try / finally rollback safety on step-3 failure.
python scripts/dev/admin_merge.py <pr-number>3. Invariants
- Toggle window minimized. Steps 2 → 3 → 4 execute in a single non-interactive sequence. The protection is in its relaxed state for the merge API call's duration only — under one second on every observed run.
- Restore is unconditional. Step 4 runs even when step 3 fails (the wrapper's
try/finallyclause ensures restoration on any exit path). A failed merge attempt does not leave the protection relaxed. - CI gating preserved throughout, while
enforce_adminsis on.required_status_checksis unaffected by the toggle; withenforce_admins: truethe merge still requires the configured status-check contexts to be green. Withenforce_admins: false, the step-3--adminmerge also bypasses red required checks, so do not run the ceremony in that state. - Audit trail visible. The
gh pr merge --adminannotation appears in the PR's merge metadata; the protection-API toggles appear in the repo's audit log under the maintainer's identity.
4. When NOT to Use the Ceremony
While the protection requires 0 approving reviews or leaves enforce_admins off, the ceremony is unnecessary: merge directly as section 1 describes.
The ceremony is for self-merge of the maintainer's own PRs. When a peer reviewer is available (a contributor with write access), the standard gh pr review --approve + gh pr merge --squash --delete-branch path is correct and the ceremony is unnecessary.
If the repository onboards a second maintainer, retire the ceremony: the standard PR-review flow restores the human-review intent of the protection without any toggle.
5. Auditing a Past Run
Every ceremony invocation lands on three durable surfaces that an operator can inspect after the fact:
- The PR's merge metadata.
gh pr view <pr-number> --json mergedBy,mergedAt,mergeCommitreturns the merge actor, the merge timestamp, and the squash commit's SHA. ThemergedByactor matches the maintainer identity that ran step 3. - The repository's audit log.
gh api /repos/<owner>/<repo>/actions/runs --jq '.workflow_runs[] | select(.event=="push")'correlates the post-merge CI run to the merge commit; the repository's protection-rule audit log (gh api /repos/<owner>/<repo>/branches/main/protectionhistory via the Settings → Branches UI) shows the step-2 and step-4 toggles bracketing the merge window. - The maintainer's git history.
git log main --first-parent --onelinelists every squash-merge commit onmainwith the corresponding PR number in the commit subject ((#<pr-number>)suffix per thegh pr merge --squashdefault), letting the operator trace any past landing back to its PR and ceremony invocation.
When scripts/dev/admin_merge.py is the entry point, its non-interactive log (stderr) names the three protection-API calls and the merge-API call in sequence; piping its stderr to tee (2>&1 | tee) captures a per-run audit fragment without depending on the GitHub-side log retention.
Cross-Machine Sync Verification Checklist
Verification procedure to confirm the Claude Code adapter installs, validates, and uninstalls correctly on a fresh host with full portability checks.
Examples
Apothem examples — worked, end-to-end walkthroughs for authoring adapters, rules, hooks, and plan suites.