From 6d0cb3bba3ae96c05776951b5115dcb1c7fdf18d Mon Sep 17 00:00:00 2001 From: William Turner Date: Sun, 23 Aug 2026 14:51:13 +0000 Subject: [PATCH 1/2] Document branch protection + dedicated bot account so the bot can only open PRs, never merge --- README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/README.md b/README.md index 86fe219..dce7000 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,32 @@ docker network create web 13. **Firewall**: `sudo ufw allow 80/tcp 443/tcp`; once the HTTPS routes above are all confirmed working, `sudo ufw delete allow 3000/tcp` and `sudo ufw delete allow 9443/tcp`. +14. **Branch protection on `main`**, on every repo you want the bot working on (so it's + structurally limited to opening PRs, never merging or pushing directly) — via the repo's + Settings → Branches → Add Rule, or the API: + ```bash + curl -X POST -H "Authorization: token $GITEA_TOKEN" -H "Content-Type: application/json" \ + "$GITEA_URL/api/v1/repos///branch_protections" \ + -d '{ + "rule_name": "main", + "enable_push": false, + "enable_merge_whitelist": true, + "merge_whitelist_usernames": [""] + }' + ``` + This blocks *all* direct pushes to `main` (everyone goes through a PR) and restricts + merging to the human usernames listed — `claude-bot` (see below) is never in that list, + so it structurally cannot merge, only open PRs, regardless of what its token can do. + +15. **Dedicated bot account** (recommended over using your own account/token for the + agent): create a separate Gitea user (e.g. `claude-bot`) via Site Administration → User + Accounts, generate its own token (scopes: repository, issue, package — no `admin`, + `organization`, or `user` needed), add it as a repo Collaborator with **Write** + permission on each automated repo, then use its token as `GITEA_TOKEN` (agent) and + `REGISTRY_TOKEN` (CI secret) instead of your own. This makes every PR comment, branch, + and PR clearly attributed to the bot instead of you, and its access is easy to revoke + independently. + ## Smoke test - Open a throwaway PR on a repo with the PR webhook set → expect a Claude review comment. From fc53251285d23bd8d7614817de6ce7254f300b57 Mon Sep 17 00:00:00 2001 From: William Turner Date: Sun, 23 Aug 2026 15:06:57 +0000 Subject: [PATCH 2/2] Replace AutojoinRoomsMixin with a lightweight invite handler Same root cause as the earlier whoami fix: the mixin's initial /joined_rooms scan 404s against Continuwuity even though the endpoint works fine when called directly. Only auto-join-on-invite was actually needed, so a plain room.invite listener replaces the whole mixin. --- agent/src/matrixBot.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/agent/src/matrixBot.js b/agent/src/matrixBot.js index 980bc8c..d474443 100644 --- a/agent/src/matrixBot.js +++ b/agent/src/matrixBot.js @@ -1,4 +1,4 @@ -import { MatrixClient, SimpleFsStorageProvider, AutojoinRoomsMixin } from "matrix-bot-sdk"; +import { MatrixClient, SimpleFsStorageProvider } from "matrix-bot-sdk"; import { runChatTask } from "./runner.js"; import { askOpenRouter, DEFAULT_MODEL } from "./openrouter.js"; @@ -45,7 +45,19 @@ export async function startMatrixBot() { const storage = new SimpleFsStorageProvider("/workspace/matrix-bot-storage.json"); const client = new MatrixClient(HOMESERVER_URL, ACCESS_TOKEN, storage); - AutojoinRoomsMixin.setupOnClient(client); + + // Not using AutojoinRoomsMixin: it calls /joined_rooms at startup to build its initial + // state, which — like the /whoami call removed earlier — 404s against Continuwuity for + // reasons unrelated to the endpoint itself (curling it directly works fine). This + // simpler handler does the one thing we actually need — auto-join on invite — without + // that startup scan. + client.on("room.invite", async (roomId) => { + try { + await client.joinRoom(roomId); + } catch (err) { + console.error("failed to join invited room", roomId, err.message); + } + }); client.on("room.message", async (roomId, event) => { // Invite-only control room enforces who can reach the bot at all; this just scopes