- 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
9.5 KiB
| name | description | version | author | platforms | metadata | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| forgejo-operations | Manage self-hosted Forgejo/Gitea instances — repos, users, SSH keys, tokens, collaborators, API operations. | 1.0.0 | Sam & Hermes |
|
|
Forgejo Operations
Manage a self-hosted Forgejo instance via API and SSH. Covers repo creation, user management, SSH key registration, collaborator permissions, and token lifecycle.
Instance Details
- URL:
https://code.smilepowered.org - SSH: port 2222, user
git - SSH config entry:
Host code.smilepowered.orgwithPort 2222 - API base:
https://code.smilepowered.org/api/v1
PR Creation + Merge (API token)
When branch protection blocks direct push, use the API token to create
and merge PRs programmatically. Token needs write:repository scope
(minimum). write:organization only needed for adjusting branch
protection rules.
Rule: No auto-merge until cross-platform validation is posted.
For Colibri: Linux cargo test --workspace AND FreeBSD/OSA
cargo test --workspace must both pass before merging.
source ~/.hermes/.env 2>/dev/null
API="https://code.smilepowered.org/api/v1/repos/<owner>/<repo>"
# Create PR
PR=$(curl -s -X POST -H "Authorization: token $FORGEJO_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"feat: ...","head":"branch-name","base":"main","body":"..."}' \
"$API/pulls")
NUM=$(echo "$PR" | python3 -c "import sys,json; print(json.load(sys.stdin)['number'])")
# Merge (returns HTTP 200 with empty body on success)
curl -s -X POST -H "Authorization: token $FORGEJO_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"Do":"merge","delete_branch_after_merge":true}' "$API/pulls/$NUM/merge"
# Verify (merge response is empty on success — use GET to confirm)
curl -s -H "Authorization: token $FORGEJO_API_TOKEN" "$API/pulls/$NUM" | \
python3 -c "import sys,json; d=json.load(sys.stdin); print(f'merged: {d[\"merged\"]}')"
Merge parameter casing: Forgejo requires "Do" (capital D), not "do".
Lowercase returns HTTP 422 [Do]: Required.
Repo Creation
# Under user
curl -X POST "$API/user/repos" \
-H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"repo-name","description":"...","private":false}'
# Under org (needs write:organization scope)
curl -X POST "$API/orgs/<org>/repos" \
-H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"repo-name","description":"...","private":false}'
User Management (admin scope required)
# Create user
curl -X POST "$API/admin/users" \
-H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
-d '{"username":"agent-name","email":"agent@example.com","password":"random","must_change_password":false,"send_notify":false}'
# Add SSH key to another user's account
curl -X POST "$API/admin/users/<username>/keys" \
-H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
-d '{"key":"ssh-ed25519 AAAA...","title":"key-label","read_only":false}'
# List SSH keys on your own account
curl "$API/user/keys" -H "Authorization: token $TOKEN"
# Delete SSH key from your own account
curl -X DELETE "$API/user/keys/<id>" -H "Authorization: token $TOKEN"
Collaborator Management
# Add/update collaborator with write permission
curl -X PUT "$API/repos/<owner>/<repo>/collaborators/<username>" \
-H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
-d '{"permission":"write"}'
# Returns 204 on success (no body)
# Needs token with write:repository on that repo
Branch Protection & Force Push
Clawdie repos have branch protection on main: direct push rejected with
pre-receive hook declined. All changes go through pull requests.
Stacked branch pitfall
Always branch from main. If a branch is created from another feature branch,
its PR includes all parent commits. Fix with cherry-pick onto a clean branch:
git checkout -b clean-branch forgejo/main
git cherry-pick <commit1> <commit2>
git diff --check forgejo/main...HEAD # verify clean
Pitfalls
Push-to-create is usually off
Forgejo rejects shallow update not allowed. Fixes:
git fetch --unshallow origin(needs full history from origin)- If origin unreachable:
git filter-branchto make first commit rootless, then push. This rewrites history — only for migration cutover. - Best: fresh full clone from Forgejo itself.
Commit hooks timing out
Some repos have pre-commit/prepare-commit-msg hooks that need Node or other
tools not on PATH. Use -c core.hooksPath=/dev/null to bypass all hooks:
git -c core.hooksPath=/dev/null commit -m "message"
Not an org — check first
/api/v1/users/<name> returns a user, /api/v1/orgs/<name> returns an org.
If you get "user redirect does not exist" on org creation, the name is already
a user. User and org can't share names in Forgejo.
SSH key on wrong account
To verify which user a key belongs to: ssh -T git@host. The greeting
("Hi there, !") tells you. If wrong, delete from current account,
re-add to correct one via admin API.
PR State Inspection
source ~/.hermes/.env 2>/dev/null
API="https://code.smilepowered.org/api/v1/repos/<owner>/<repo>"
# List open PRs
curl -s -H "Authorization: token $FORGEJO_API_TOKEN" "$API/pulls?state=open"
# Check specific PR state
curl -s -H "Authorization: token $FORGEJO_API_TOKEN" "$API/pulls/$PR_NUMBER" | \
python3 -c "import sys,json; d=json.load(sys.stdin); print(f'#{d[\"number\"]}: state={d[\"state\"]}, merged={d[\"merged\"]}, mergeable={d.get(\"mergeable\",\"?\")}')"
Pitfall: read pipe loses variable in subshell. Piping into read (e.g.
echo "$PR" | python3 -c "..." | read NUM) silently fails — the variable
is set in a subshell and lost when the pipe exits. Use command substitution
instead: NUM=$(echo "$PR" | python3 -c "...").
Merge rate-limit ("Please try again later")
Forgejo may return HTTP 200 with {\"message\":\"Please try again later\"}
on the merge endpoint. Wait 3-5 seconds and retry. If it persists after
3 retries, fall back to web UI merge. This appears to be backend-side
processing delay, not a token scope issue.
Pitfall: HTTP 405 on merge = PR not mergeable OR already merged. HTTP 405 from the merge endpoint means either (a) the PR has merge conflicts Forgejo can't auto-resolve, or (b) the PR was already merged (merge on an already-merged PR returns 405, not 409). Always check PR state before retrying — the merge may have succeeded silently on a prior call (Forgejo sometimes returns empty 200/204 on success, which trips up JSON parsers). Verify with:
curl -s -H "Authorization: token $TOKEN" "$API/pulls/$NUM" | \
python3 -c "import sys,json; d=json.load(sys.stdin); print(f'mergeable: {d[\"mergeable\"]}')"
If mergeable: false, conflicts exist. Resolution options:
- Web UI: visit the PR page and use Forgejo's conflict resolver
- Local resolve:
git merge, fix conflicts, push resolved branch to a new ref, open fresh PR - Superseding branch: if another branch already contains the same fixes plus more, close this PR and point to the newer branch
Do NOT retry the merge API — 405 on an unmergeable PR will never self-resolve. This is different from the transient "Please try again later" rate-limit response which returns HTTP 200 with an error message body.
Pitfall: token display when used in-shell. When using the token in curl
commands via a shell script, load it from env rather than pasting inline.
source ~/.hermes/.env works if .env is mode 0600. If the token value appears
in the command string (even with export TOKEN=...), the agent may redact it
mid-command, resulting in truncated tokens. Use source ~/.hermes/.env before
the curl call, or store it in $FORGEJO_API_TOKEN and never print it.
Subtopics
This skill covers Forgejo operations end-to-end. For deep dives into specific workflows, see:
| Topic | Reference | Description |
|---|---|---|
| Agent Onboarding | references/agent-onboarding.md |
Create users, register SSH keys, add collaborators, verify access |
| Multi-Agent Infrastructure | references/multi-agent-infrastructure.md |
Architecture, setup order, SSH config, Vaultwarden integration |
| Branch Protection | references/branch-protection.md |
Web UI steps for requiring PR on main, verification |
| Branch Protection API | references/branch-protection-api.md |
Programmatic remove/re-create protection (for force push) |
| SSH Key Transfer | references/key-transfer.md |
Move an SSH key between Forgejo user accounts |
| Self-Hosted Setup | references/self-hosted-setup.md |
Codeberg migration, shallow clone fixes, password rotation |
| API Token Scopes | references/forgejo-token-scopes.md |
Complete scope reference table |
| Git Shallow Fixes | references/git-shallow-fixes.md |
Unshallowing clones, filter-branch root commits |
| Vaultwarden bw CLI | references/vaultwarden-bw-cli.md |
bw CLI login/unlock/CRUD for agent secrets |
| API Merge Pattern | references/api-merge-pattern.md |
Programmatic PR create + merge via curl |
| API Token Setup | references/api-token-setup.md |
Token creation workflow and storage |
| Colibri Validation | references/colibri-validation-commands.md |
Cross-platform validation commands |
| Git History Flatten | references/git-history-flatten.md |
Flatten merge-heavy history via first-parent cherry-pick |
Template
templates/FORGEJO-SETUP.md— Agent self-bootstrap documentation template