From e14c21fab5eee9d6780680f4fb03f015e94a1756 Mon Sep 17 00:00:00 2001 From: Semprini Date: Sun, 21 Jun 2026 18:46:33 +1200 Subject: [PATCH] Fix 429 (model pin) and bot ignoring encrypted admin replies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two independent bugs: 1. 429 "long context" — agentic sessions read enough files to exceed the 200K standard window; on the default [1m] model Synapse escalates into the paid 1M tier. Pin claude_model to claude-opus-4-8 (200K window) so Claude Code compacts context instead. Updated config.example.json and the maintainer.py default / comment accordingly. 2. Bot silently ignores operator replies — the DM room was created with is_direct:true, causing Element to auto-enable E2E encryption. This stdlib-only bot cannot decrypt m.room.encrypted events, so all admin messages were dropped without any log output. Fix: - register-matrix-bot.sh now creates rooms with is_direct:false and preset:private_chat (Synapse does not force encryption on non-DM rooms). - matrix_poll_admin() now logs a loud WARN if an encrypted event arrives, instead of silently skipping it. Co-Authored-By: Claude Sonnet 4.6 --- config.example.json | 2 +- maintainer.py | 18 +++++++++++++++--- register-matrix-bot.sh | 8 ++++++-- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/config.example.json b/config.example.json index a26c7f0..a13b288 100644 --- a/config.example.json +++ b/config.example.json @@ -4,7 +4,7 @@ "agent_file": "agents/stack-support.agent.md", "runbook_file": "docs/stack-support-runbook.md", "claude_bin": "/home/paul/.local/bin/claude", - "claude_model": null, + "claude_model": "claude-opus-4-8", "claude_extra_args": [], "agent_timeout_seconds": 1800, diff --git a/maintainer.py b/maintainer.py index 79c3225..6a5adfd 100755 --- a/maintainer.py +++ b/maintainer.py @@ -53,7 +53,11 @@ DEFAULTS = { "agent_file": "agents/stack-support.agent.md", "runbook_file": "docs/stack-support-runbook.md", "claude_bin": "/home/paul/.local/bin/claude", - "claude_model": None, # None = claude default + # Pin a standard 200K-window model. Leaving this None uses the account + # default, which may be a 1M-context ([1m]) model; once a cycle's context + # crosses 200K that escalates to the 1M tier and fails on subscription + # plans with "Usage credits are required for long context requests" (429). + "claude_model": "claude-opus-4-8", "claude_extra_args": [], "agent_timeout_seconds": 1800, "kuma_volume": "uptime-kuma-data", @@ -242,10 +246,18 @@ def matrix_poll_admin(cfg, state): if not room: return messages for ev in room.get("timeline", {}).get("events", []): - if ev.get("type") != "m.room.message": - continue if ev.get("sender") != admin: continue + # This bot is stdlib-only and cannot do E2E encryption. If the room is + # encrypted, the admin's replies arrive as undecryptable m.room.encrypted + # events — warn loudly rather than silently ignoring the operator. The + # room should be created unencrypted (see register-matrix-bot.sh). + if ev.get("type") == "m.room.encrypted": + log("WARN: received an ENCRYPTED message from the admin — this bot " + "cannot decrypt it. The maintenance room must be unencrypted.") + continue + if ev.get("type") != "m.room.message": + continue content = ev.get("content", {}) if content.get("msgtype") == "m.text": messages.append(content.get("body", "").strip()) diff --git a/register-matrix-bot.sh b/register-matrix-bot.sh index 1a1b169..6952794 100755 --- a/register-matrix-bot.sh +++ b/register-matrix-bot.sh @@ -140,10 +140,14 @@ joined = api(hs + "/_matrix/client/v3/joined_rooms", token=bot_token)[1].get("jo if room_id and not room_id.startswith("FILLED_BY_") and room_id in joined: print(f"→ Re-using existing room {room_id}") else: + # NB: is_direct must be False. A direct (DM) room makes Element auto-enable + # E2E encryption, which this stdlib-only bot cannot decrypt — it would then + # silently never see the operator's replies. A plain private room stays + # unencrypted (Synapse does not force room encryption). code, body = api(hs + "/_matrix/client/v3/createRoom", { "name": "Stack Maintenance", - "topic": "semprini-core autonomous stack maintenance", - "preset": "trusted_private_chat", "is_direct": True, + "topic": "semprini-core autonomous stack maintenance (unencrypted — bot is stdlib-only)", + "preset": "private_chat", "is_direct": False, "invite": [admin_id]}, token=bot_token) room_id = body.get("room_id") if not room_id: