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

  1. Transition the room to State::Multi before the multi-message path inserts.
  2. Verify mode selection picks exactly one mode per room and the State transition happens on the same code path.
  3. 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

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


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/0f8bf1c65c64dd3f. Report an issue: GitHub.