zeroclaw-labs/zeroclaw · error · anyhow::Error
matrix: room {recipient} is not in joined state
Error message
matrix: room {recipient} is not in joined state What it means
resolve_joined_room is the pre-send gate: it resolves the recipient to a room id, looks the Room up in the SDK cache (a missing room fails earlier as 'matrix: bot is not in room'), then requires room.state() == RoomState::Joined. A room in Invited, Left, Knocked or Banned state aborts here, because Matrix only lets a client send events to rooms it has joined.
Source
Thrown at crates/zeroclaw-channels/src/matrix.rs:3760
pub(super) async fn resolve_joined_room(
client: &Client,
cache: &Arc<TokioRwLock<HashMap<String, OwnedRoomId>>>,
recipient: &str,
) -> Result<Room> {
let id = client::resolve_room(client, cache, recipient).await?;
let room = client.get_room(&id).ok_or_else(|| {
::zeroclaw_log::record!(
ERROR,
::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Fail)
.with_outcome(::zeroclaw_log::EventOutcome::Failure)
.with_attrs(::serde_json::json!({"recipient": recipient})),
"matrix: bot is not in room"
);
anyhow::Error::msg(format!("matrix: bot is not in room {recipient}"))
})?;
if room.state() != RoomState::Joined {
bail!("matrix: room {recipient} is not in joined state");
}
Ok(room)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum AttachmentKind {
Auto,
Image,
Audio,
Video,
File,
Voice,
}
async fn upload_attachment(
room: &Room,
att: &MediaAttachment,
kind: AttachmentKind,View on GitHub (pinned to 88bb9c8533)
Solutions
- Ensure invites are accepted before dispatch: join invited rooms when the invite state appears, or have an admin re-invite the bot and let it join.
- If the bot was kicked or left, get it re-invited, re-join, then retry the send.
- After startup, wait for the first sync to complete before dispatching, or retry the send once after a short delay so room state hydrates.
- Confirm the recipient resolves to the room the bot actually occupies - parallel rooms can share a similar alias.
Example fix
// before: send immediately after startup
let room = resolve_joined_room(&client, &cache, recipient).await?;
// after: accept pending invites and let sync settle first
for invited in client.invited_rooms() {
invited.join().await.ok();
}
tokio::time::sleep(std::time::Duration::from_secs(2)).await; // allow state sync
let room = resolve_joined_room(&client, &cache, recipient).await?; Defensive patterns
Strategy: validation
Validate before calling
async fn ensure_joined(client: &Client, room_id: &str) -> anyhow::Result<()> {
let id: OwnedRoomId = room_id.parse()?;
let room = client
.get_room(&id)
.ok_or_else(|| anyhow::anyhow!("room {room_id} not in cache"))?;
if room.state() != RoomState::Joined {
room.join().await?; // accepts a pending invite or re-joins a left room
anyhow::ensure!(room.state() == RoomState::Joined, "still not joined: {room_id}");
}
Ok(())
} Prevention
- Handle invite state changes in the sync loop: auto-join rooms from allowlisted peers.
- Gate outbound queues on a completed initial sync.
- Monitor membership kicks via sync logs so operators can re-invite before sends fail.
- Retry once on this error after a short delay - cache hydration races are the benign case.
When it happens
Trigger: Sending or reacting when the bot's cached state for the room is anything but Joined: an invite not yet accepted (Invited), the bot left or was kicked (Left/Banned), or the state cache has not hydrated from the initial sync yet at startup.
Common situations: Freshly invited bot whose auto-join has not run or is disabled; bot kicked mid-conversation while queued messages were pending; sends issued immediately after startup before the first sync completes; stale room cache after re-login.
Related errors
- matrix: configured message_max_bytes cannot contain a thread
- Gmail OAuth token is not configured for sending
- Invalid iMessage target: must be a phone number (+1234567890
- send failed {context}: status={status}, body={body}
- send failed after token refresh: status={retry_status}, body
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/b69f13707f1bb3dc.
Report an issue: GitHub.