- SOUL.md: full agent identity, operating principles, voice - IDENTITY.md: runtime identity, hosts, boundaries - USER.md: operator context imported from hermes-soul - AGENTS.md: actual operating rules, infrastructure, quick reference - memories/curated/: 5 topics (tailscale, forgejo, agents, projects, vaultwarden) - skills/: 9 cross-harness skills imported from hermes-soul after review - docs/PLAN-CONFIGURE-PRIVATE-REPO.md: configuration plan - Validate: passes clean
2.8 KiB
2.8 KiB
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:organizationscope (branch protection management) - Token stored in
~/.hermes/.envasFORGEJO_API_TOKEN
source ~/.hermes/.env 2>/dev/null
API="https://code.smilepowered.org/api/v1/repos/<owner>/<repo>"
List Current Protections
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)
# 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)
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)
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
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
idfield. 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:repositoryis NOT enough for branch protection API — you needwrite:organization. The day-to-day agent token (write:repositoryonly) 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_approvalsorenable_status_check, the repo is under-protected until fixed.