zeroclaw-labs/zeroclaw · error · anyhow::Error
matrix: partial draft state unavailable
Error message
matrix: partial draft state unavailable
What it means
Matrix draft bookkeeping keeps a per-room State enum with distinct variants for partial, single-message and multi-message draft modes. insert_partial may only run while the room is in State::Partial; the let-else destructure fails on any other variant and aborts. The error signals a mode mismatch: a partial draft was recorded after the room already moved to (or never entered) partial mode.
Source
Thrown at crates/zeroclaw-channels/src/matrix.rs:487
impl State {
/// Create the draft store matching the immutable Matrix stream mode.
pub(super) fn for_stream_mode(mode: MatrixStreamMode) -> Self {
match mode {
MatrixStreamMode::Off => Self::Off,
MatrixStreamMode::Partial => Self::Partial(HashMap::new()),
MatrixStreamMode::SingleMessage => Self::Single(HashMap::new()),
MatrixStreamMode::MultiMessage => Self::Multi(HashMap::new()),
}
}
}
pub(super) fn insert_partial(
state: &mut State,
key: DraftKey,
draft: PartialDraft,
) -> Result<()> {
let State::Partial(drafts) = state else {
bail!("matrix: partial draft state unavailable");
};
drafts.insert(key, draft);
Ok(())
}
pub(super) fn partial_for_update<'a>(
state: &'a mut State,
key: &DraftKey,
) -> Option<&'a mut PartialDraft> {
match state {
State::Partial(drafts) => drafts.get_mut(key),
_ => None,
}
}
pub(super) fn take_partial(state: &mut State, key: &DraftKey) -> Option<PartialDraft> {
match state {
State::Partial(drafts) => drafts.remove(key),View on GitHub (pinned to 88bb9c8533)
Solutions
- Check or drive the State variant before inserting: only call insert_partial once the room is in Partial mode.
- Audit the transition that moved the state out of Partial (finalize/cancel/upgrade) and make it drain pending partials or make the insert path variant-aware.
- In tests, construct the state in the Partial variant before calling insert_partial.
Example fix
// before
drafts::insert_partial(&mut state, key, draft)?;
// after
if !matches!(state, State::Partial(_)) {
state = State::Partial(Default::default());
}
drafts::insert_partial(&mut state, key, draft)?; Defensive patterns
Strategy: type-guard
Type guard
fn in_partial_state(state: &State) -> bool {
matches!(state, State::Partial(_))
} Prevention
- Give each room's draft lifecycle a single owner so inserts cannot race a finalize or cancel.
- Transition the room into the target draft mode and insert within the same critical section.
- Use the variant-specific lookup helpers (partial_for_update and friends) so the expected mode is explicit at every call site.
When it happens
Trigger: Calling insert_partial(state, key, draft) when state is not State::Partial - e.g. the room already began a single-message or multi-message lifecycle, or its state was finalized/cancelled and reset, while a partial-draft path still tries to insert.
Common situations: Two draft lifecycles racing on one room (interleaved update/finalize); a state transition that moves the room out of Partial without draining pending partial inserts; tests driving insert_partial against a freshly constructed non-Partial state (the *_for_same_room_concurrency tests pin this).
Related errors
- matrix: single-message draft state unavailable
- matrix: multi-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/179c30267a711904.
Report an issue: GitHub.