Fix 429 (model pin) and bot ignoring encrypted admin replies
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 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
"agent_file": "agents/stack-support.agent.md",
|
"agent_file": "agents/stack-support.agent.md",
|
||||||
"runbook_file": "docs/stack-support-runbook.md",
|
"runbook_file": "docs/stack-support-runbook.md",
|
||||||
"claude_bin": "/home/paul/.local/bin/claude",
|
"claude_bin": "/home/paul/.local/bin/claude",
|
||||||
"claude_model": null,
|
"claude_model": "claude-opus-4-8",
|
||||||
"claude_extra_args": [],
|
"claude_extra_args": [],
|
||||||
"agent_timeout_seconds": 1800,
|
"agent_timeout_seconds": 1800,
|
||||||
|
|
||||||
|
|||||||
+15
-3
@@ -53,7 +53,11 @@ DEFAULTS = {
|
|||||||
"agent_file": "agents/stack-support.agent.md",
|
"agent_file": "agents/stack-support.agent.md",
|
||||||
"runbook_file": "docs/stack-support-runbook.md",
|
"runbook_file": "docs/stack-support-runbook.md",
|
||||||
"claude_bin": "/home/paul/.local/bin/claude",
|
"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": [],
|
"claude_extra_args": [],
|
||||||
"agent_timeout_seconds": 1800,
|
"agent_timeout_seconds": 1800,
|
||||||
"kuma_volume": "uptime-kuma-data",
|
"kuma_volume": "uptime-kuma-data",
|
||||||
@@ -242,10 +246,18 @@ def matrix_poll_admin(cfg, state):
|
|||||||
if not room:
|
if not room:
|
||||||
return messages
|
return messages
|
||||||
for ev in room.get("timeline", {}).get("events", []):
|
for ev in room.get("timeline", {}).get("events", []):
|
||||||
if ev.get("type") != "m.room.message":
|
|
||||||
continue
|
|
||||||
if ev.get("sender") != admin:
|
if ev.get("sender") != admin:
|
||||||
continue
|
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", {})
|
content = ev.get("content", {})
|
||||||
if content.get("msgtype") == "m.text":
|
if content.get("msgtype") == "m.text":
|
||||||
messages.append(content.get("body", "").strip())
|
messages.append(content.get("body", "").strip())
|
||||||
|
|||||||
@@ -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:
|
if room_id and not room_id.startswith("FILLED_BY_") and room_id in joined:
|
||||||
print(f"→ Re-using existing room {room_id}")
|
print(f"→ Re-using existing room {room_id}")
|
||||||
else:
|
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", {
|
code, body = api(hs + "/_matrix/client/v3/createRoom", {
|
||||||
"name": "Stack Maintenance",
|
"name": "Stack Maintenance",
|
||||||
"topic": "semprini-core autonomous stack maintenance",
|
"topic": "semprini-core autonomous stack maintenance (unencrypted — bot is stdlib-only)",
|
||||||
"preset": "trusted_private_chat", "is_direct": True,
|
"preset": "private_chat", "is_direct": False,
|
||||||
"invite": [admin_id]}, token=bot_token)
|
"invite": [admin_id]}, token=bot_token)
|
||||||
room_id = body.get("room_id")
|
room_id = body.get("room_id")
|
||||||
if not room_id:
|
if not room_id:
|
||||||
|
|||||||
Reference in New Issue
Block a user