zeroclaw-labs/zeroclaw · error · anyhow::Error
matrix: multi-message draft state unavailable
Error message
matrix: multi-message draft state unavailable
What it means
insert_multi is the multi-message counterpart in the per-room draft State enum: it may only run while the room is in State::Multi, the mode where one reply is split across several sequential messages. The let-else destructure fails on Partial/Single or any other variant and aborts, signalling that a multi-message draft was recorded against a room not in multi-message mode.
Source
Thrown at crates/zeroclaw-channels/src/matrix.rs:563
}
/// Remove the `single_message` draft from live state at finalize/cancel so
/// a late progress update cannot keep editing a completed response.
pub(super) fn take_single(state: &mut State, key: &DraftKey) -> Option<SingleDraft> {
match state {
State::Single(drafts) => drafts.remove(key),
_ => None,
}
}
#[cfg(test)]
pub(super) fn single_contains(state: &State, key: &DraftKey) -> bool {
matches!(state, State::Single(drafts) if drafts.contains_key(key))
}
pub(super) fn insert_multi(state: &mut State, key: DraftKey, draft: MultiDraft) -> Result<()> {
let State::Multi(drafts) = state else {
bail!("matrix: multi-message draft state unavailable");
};
drafts.insert(key, draft);
Ok(())
}
pub(super) fn multi_for_update<'a>(
state: &'a mut State,
key: &DraftKey,
) -> Option<&'a mut MultiDraft> {
match state {
State::Multi(drafts) => drafts.get_mut(key),
_ => None,
}
}
pub(super) fn take_multi(state: &mut State, key: &DraftKey) -> Option<MultiDraft> {
match state {
State::Multi(drafts) => drafts.remove(key),View on GitHub (pinned to 88bb9c8533)
Solutions
- Transition the room to State::Multi before the multi-message path inserts.
- Verify mode selection picks exactly one mode per room and the State transition happens on the same code path.
- In tests, build the state in the Multi variant before calling insert_multi.
Example fix
// before
drafts::insert_multi(&mut state, key, draft)?;
// after
if !matches!(state, State::Multi(_)) {
state = State::Multi(Default::default());
}
drafts::insert_multi(&mut state, key, draft)?; Defensive patterns
Strategy: type-guard
Type guard
fn in_multi_state(state: &State) -> bool {
matches!(state, State::Multi(_))
} Prevention
- Negotiate exactly one draft mode per room before any insert runs.
- Keep the mode transition and the first insert on the same code path.
- Build test state in the Multi variant when testing multi-message lifecycle isolation.
When it happens
Trigger: Calling insert_multi(state, key, draft) when state is not State::Multi - the room is in Partial or Single draft mode while the multi-message lifecycle tries to register its draft.
Common situations: Mode negotiation selecting multi-message for a room that still holds partial/single state (transition missing); concurrent lifecycles on one room; tests for multi-message update/finalize/cancel isolation constructing the wrong initial variant.
Related errors
- matrix: partial draft state unavailable
- matrix: single-message draft state unavailable
- matrix: draft message id is empty
- matrix: corruption recovery looped — aborting to avoid an in
- matrix: {reason} Cannot auto-recover because channels.matrix
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/0f8bf1c65c64dd3f.
Report an issue: GitHub.