# Forgejo Branch Protection API For history rewrites (flattening, squashing, filter-branch) when main is protected, you must temporarily remove branch protection, force push, then re-create it. The Forgejo API identifies protections by branch name, not by ID. ## Prerequisites - Token with `write:organization` scope (branch protection management) - Token stored in `~/.hermes/.env` as `FORGEJO_API_TOKEN` ```bash source ~/.hermes/.env 2>/dev/null API="https://code.smilepowered.org/api/v1/repos//" ``` ## List Current Protections ```bash curl -s -H "Authorization: token $FORGEJO_API_TOKEN" \ "$API/branch_protections" | python3 -m json.tool ``` Returns an array of protection objects. Each has `branch_name`, `rule_name`, `enable_push` (false = protected), and other fields. ## Remove Protection (temporarily) ```bash # DELETE by branch name — returns HTTP 204 on success curl -s -X DELETE -H "Authorization: token $FORGEJO_API_TOKEN" \ "$API/branch_protections/main" -w "\nHTTP %{http_code}\n" ``` Only 204 means success. Any other code = protection still active. ## Force Push (while unprotected) ```bash git push --force-with-lease forgejo main ``` Use `--force-with-lease` not `--force` — it refuses if someone pushed since your last fetch. ## Re-Create Protection (immediately after push) ```bash curl -s -X POST -H "Authorization: token $FORGEJO_API_TOKEN" \ -H "Content-Type: application/json" \ "$API/branch_protections" \ -d '{ "branch_name": "main", "rule_name": "main", "enable_push": false, "enable_push_whitelist": false }' -w "\nHTTP %{http_code}\n" ``` HTTP 201 = created. Match the original protection settings — the above is the minimal "no direct push, no whitelist" config used by Clawdie repos. ## Verification ```bash curl -s -H "Authorization: token $FORGEJO_API_TOKEN" \ "$API/branch_protections" | python3 -c " import sys, json for p in json.load(sys.stdin): if p['branch_name'] == 'main': print(f'main: enable_push={p[\"enable_push\"]}') " ``` Should show `enable_push=False` (protected). ## Pitfalls - **Forgejo IDs protections by branch name, not numeric ID.** The GET response has no `id` field. Use the branch name as the DELETE key. - **Must re-create immediately.** The window between DELETE and re-POST is an unprotected window — anyone can push directly. Keep it as short as possible. On a fast connection: < 2 seconds. - **Token scope matters.** `write:repository` is NOT enough for branch protection API — you need `write:organization`. The day-to-day agent token (`write:repository` only) cannot do this. Use a token with org scope or temporarily re-scope. - **Matched settings on re-create.** Copy the original protection object fields exactly. If you lose settings like `required_approvals` or `enable_status_check`, the repo is under-protected until fixed.