branch-protection
This skill manages GitHub branch protection for the `main` branch by syncing a checked-in JSON specification with GitHub's ruleset system, preventing accidental force-pushes, deletions, and unreviewed merges while allowing repository admins to bypass rules when necessary. Use it when you need reproducible, code-reviewed control over branch protection rules that can be audited, restored, and applied consistently without manual GitHub UI edits.
git clone --depth 1 https://github.com/piprail/piprail /tmp/branch-protection && cp -r /tmp/branch-protection/.claude/skills/branch-protection ~/.claude/skills/branch-protectionSKILL.md
# Branch protection — protect `main`, as code
**The single source of truth for how `main` is locked down, and the only safe way to change it.**
The live config is a GitHub **ruleset** (the modern replacement for classic branch protection).
This skill mirrors it into a checked-in JSON spec so the protection is reviewable, reproducible,
and restorable — and every command here is **back-up-first, verify-after**.
> **Golden rule: never click-edit the ruleset in the GitHub UI for a real change.** Edit
> [`protect-main.json`](protect-main.json), run **Apply**, let it verify. The UI is for one-off
> inspection only; the JSON is the truth.
---
## 0. Mental model — what this does and does NOT defend against
Read this before touching anything; it stops you from "fixing" a non-problem.
- **A fork cannot touch your `main`.** A fork is a separate copy under someone else's account.
An **issue** is a comment thread. Neither changes a byte of your repo. The only way outside
code lands is **you merging their pull request** by hand. GitHub's "unprotected branch" nudge
is generic best-practice, not an alarm about a specific person.
- **Outsiders already can't push or merge** — that requires collaborator write access, which
no one but the owner has. Branch protection does **not** grant "only I can merge"; that's
already true. What it adds is discipline against the *real* risks:
1. **You** force-pushing or committing straight to `main` (history-corruption footgun — see
[[git-shallow-clone-amend-danger]]).
2. A **future collaborator** doing the same.
3. `main` being **deleted**.
4. Code merging with **red CI** (only if you opt into required status checks — see §6 caveat).
So the protection here is mostly "protect the maintainer from their own accidents," and that is
exactly what a solo-maintainer OSS repo wants.
---
## 1. The facts (PipRail, current)
| Thing | Value |
|---|---|
| Repo | `piprail/piprail` (org-owned, public) |
| Ruleset name | `protect-main` |
| Ruleset id | `17756558` |
| Target | `~DEFAULT_BRANCH` (follows `main` even if renamed) |
| Enforcement | `active` |
| Rules | `deletion` · `non_fast_forward` (block force-push) · `pull_request` (0 approvals, all merge methods) · `required_signatures` |
| Bypass | **Repository admin role** (`actor_id 5`, `RepositoryRole`), mode `always` — this is what keeps you from locking yourself out |
| Classic branch protection | **none** (intentional — the ruleset is the only source of truth; don't add classic on top, they stack confusingly) |
| `gh` account needed | admin on `piprail/piprail` (currently `John-Weeks-Dev`) |
If the ruleset id ever changes (deleted + recreated), rediscover it:
```bash
gh api repos/piprail/piprail/rulesets --jq '.[] | select(.name=="protect-main") | .id'
```
---
## 2. Read-only — always safe, run these freely
### Status (human-readable)
```bash
gh api repos/piprail/piprail/rulesets/17756558 --jq '"name: \(.name)
enforcement: \(.enforcement)
targets: \(.conditions.ref_name.include)
rules: \([.rules[].type]|join(", "))
bypass: \([.bypass_actors[]|"role#\(.actor_id) (\(.bypass_mode))"]|join(", "))
can I bypass?: \(.current_user_can_bypass)"'
```
### Audit — does live match the committed spec? (drift detection)
```bash
SPEC=.claude/skills/branch-protection/protect-main.json
norm() { jq -S '{name,target,enforcement,conditions:.conditions,rules:(.rules|sort_by(.type)),bypass:(.bypass_actors|sort_by(.actor_id))}'; }
diff -u <(norm < "$SPEC") <(gh api repos/piprail/piprail/rulesets/17756558 | norm) \
&& echo "✓ IN SYNC — live protection matches the committed spec." \
|| echo "⚠ DRIFT — live differs from the spec (review the diff above)."
```
A clean `✓ IN SYNC` is the green light. Any diff means someone changed the ruleset in the UI
(or the spec was edited but not applied) — reconcile before doing anything else.
### Backup — snapshot the live ruleset to a timestamped file
```bash
mkdir -p .claude/skills/branch-protection/backups
gh api repos/piprail/piprail/rulesets/17756558 \
> ".claude/skills/branch-protection/backups/protect-main.$(date +%Y%m%d-%H%M%S).json"
```
> `backups/` is for local safety snapshots — keep it gitignored or prune it; it is not the
> source of truth (`protect-main.json` is).
---
## 3. Apply — the ONLY safe way to change protection
Edit [`protect-main.json`](protect-main.json) first, then run this. It **(a)** refuses to proceed
unless the spec still keeps your admin bypass and active enforcement, **(b)** backs up the live
state, **(c)** applies, **(d)** verifies the result matches the spec, and **(e)** auto-restores
from the backup if anything diverges. This is proven idempotent — re-applying the current spec
changes nothing.
```bash
set -euo pipefail
RS=17756558; REPO=piprail/piprail
SPEC=.claude/skills/branch-protection/protect-main.json
# (a) SAFETY GUARDS — abort rather than risk a lockout / silent disable
jq -e '.bypass_actors[]? | select(.actor_id==5 and .actor_type=="RepositoryRole")' "$SPEC" >/dev/null \
|| { echo "✗ ABORT: spec drops the admin bypass — you could lock yourself out. Add it back."; exit 1; }
[ "$(jq -r .enforcement "$SPEC")" = "active" ] \
|| { echo "✗ ABORT: spec enforcement is not 'active' — protection would be off. Intentional? Edit by hand."; exit 1; }
# (b) backup
mkdir -p .claude/skills/branch-protection/backups
BK=".claude/skills/branch-protection/backups/protect-main.$(date +%Y%m%d-%H%M%S).json"
gh api "repos/$REPO/rulesets/$RS" > "$BK"; echo "✓ backed up → $BK"
# (c) apply
gh api --method PUT "repos/$REPO/rulesets/$RS" --input "$SPEC" >/dev/null && echo "✓ applied"
# (d) verify
norm() { jq -S '{name,target,enforcement,conditions:.conditions,rules:(.rules|sort_by(.type)),bypass:(.bypass_actors|sort_by(.actor_id))}'; }
if diff -u <(norm < "$SPEC") <(gh api "repos/$REPO/rulesets/$RS" | norm); then
echo "✓✓✓ VERIFIED — live now matches the spec."
else
# (e) auto-restore
echo "‼️ MISMATCH — restoring from backup…"
gh apiAdd a new chain, EVM preset, driver family, or token to PipRail (SDK + site)
>-
>-
>-
>-
>-
A self-custodial crypto payment wallet for your OpenClaw agent, with a hard spend cap it can't exceed. It autonomously pays x402 paywalls ('402 Payment Required') on every major chain, settling funds straight to the recipient — no facilitator, no fee, no signup. Runs read-only with no key (discover/quote/plan before you add a wallet); add a key only to let the agent pay, optionally gaslessly via the standard x402 'exact' rail.