zeroclaw-labs/zeroclaw · error · anyhow::Error

matrix: single-message draft state unavailable

Error message

matrix: single-message draft state unavailable

What it means

Matrix draft bookkeeping keeps a per-room State enum; insert_single may only run while the room is in State::Single (single-message progress mode, where one message is repeatedly edited in place). The let-else destructure fails on any other variant and aborts. The error means a single-message draft was recorded while the room's draft mode was partial or multi, or otherwise not Single.

Source

Thrown at crates/zeroclaw-channels/src/matrix.rs:529

    pub(super) fn partial_contains(state: &State, key: &DraftKey) -> bool {
        matches!(state, State::Partial(drafts) if drafts.contains_key(key))
    }

    #[cfg(test)]
    pub(super) fn partial_len(state: &State) -> usize {
        match state {
            State::Partial(drafts) => drafts.len(),
            _ => 0,
        }
    }

    pub(super) fn insert_single(
        state: &mut State,
        key: DraftKey,
        draft: SingleDraft,
    ) -> Result<()> {
        let State::Single(drafts) = state else {
            bail!("matrix: single-message draft state unavailable");
        };
        drafts.insert(key, draft);
        Ok(())
    }

    /// Return the editable `single_message` draft for this room+draft id, if it
    /// is still active.
    pub(super) fn single_for_update<'a>(
        state: &'a mut State,
        key: &DraftKey,
    ) -> Option<&'a mut SingleDraft> {
        match state {
            State::Single(drafts) => drafts.get_mut(key),
            _ => None,
        }
    }

    /// Remove the `single_message` draft from live state at finalize/cancel so

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Transition the room to State::Single before the single-message update path inserts.
  2. Audit mode selection: one room must not be treated as single-message while partial/multi drafts exist.
  3. In tests, build the state in the Single variant before calling insert_single.

Example fix

// before
drafts::insert_single(&mut state, key, draft)?;

// after
if !matches!(state, State::Single(_)) {
    state = State::Single(Default::default());
}
drafts::insert_single(&mut state, key, draft)?;
Defensive patterns

Strategy: type-guard

Type guard

fn in_single_state(state: &State) -> bool {
    matches!(state, State::Single(_))
}

Prevention

When it happens

Trigger: Calling insert_single(state, key, draft) when state is not State::Single - the room is still in Partial streaming mode or a multi-message lifecycle owns it, while the single-message update path registers its draft.

Common situations: Flipping a room between progress modes (partial to single) while a previous mode's inserts are still in flight; refactors that changed mode-selection logic; tests for single-message edit/redact/flush semantics constructing the wrong initial state.

Related errors


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