{"record":{"id":"0f8bf1c65c64dd3f","repo":"zeroclaw-labs/zeroclaw","slug":"matrix-multi-message-draft-state-unavailable","errorCode":null,"errorMessage":"matrix: multi-message draft state unavailable","messagePattern":"matrix: multi-message draft state unavailable","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/matrix.rs","lineNumber":563,"sourceCode":"    }\n\n    /// Remove the `single_message` draft from live state at finalize/cancel so\n    /// a late progress update cannot keep editing a completed response.\n    pub(super) fn take_single(state: &mut State, key: &DraftKey) -> Option<SingleDraft> {\n        match state {\n            State::Single(drafts) => drafts.remove(key),\n            _ => None,\n        }\n    }\n\n    #[cfg(test)]\n    pub(super) fn single_contains(state: &State, key: &DraftKey) -> bool {\n        matches!(state, State::Single(drafts) if drafts.contains_key(key))\n    }\n\n    pub(super) fn insert_multi(state: &mut State, key: DraftKey, draft: MultiDraft) -> Result<()> {\n        let State::Multi(drafts) = state else {\n            bail!(\"matrix: multi-message draft state unavailable\");\n        };\n        drafts.insert(key, draft);\n        Ok(())\n    }\n\n    pub(super) fn multi_for_update<'a>(\n        state: &'a mut State,\n        key: &DraftKey,\n    ) -> Option<&'a mut MultiDraft> {\n        match state {\n            State::Multi(drafts) => drafts.get_mut(key),\n            _ => None,\n        }\n    }\n\n    pub(super) fn take_multi(state: &mut State, key: &DraftKey) -> Option<MultiDraft> {\n        match state {\n            State::Multi(drafts) => drafts.remove(key),","sourceCodeStart":545,"sourceCodeEnd":581,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/matrix.rs#L545-L581","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\ndrafts::insert_multi(&mut state, key, draft)?;\n\n// after\nif !matches!(state, State::Multi(_)) {\n    state = State::Multi(Default::default());\n}\ndrafts::insert_multi(&mut state, key, draft)?;","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"fn in_multi_state(state: &State) -> bool {\n    matches!(state, State::Multi(_))\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["matrix","draft","state-machine","enum-variant","multi-message"],"backgroundTag":"illegal-state-transition","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}