{"record":{"id":"e957053f05a25922","repo":"zeroclaw-labs/zeroclaw","slug":"matrix-draft-message-id-is-empty","errorCode":null,"errorMessage":"matrix: draft message id is empty","messagePattern":"matrix: draft message id is empty","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/matrix.rs","lineNumber":397,"sourceCode":"    use matrix_sdk::ruma::{OwnedEventId, OwnedRoomId};\n    use zeroclaw_runtime::agent::loop_::{\n        DRAFT_PLACEHOLDER, REASONING_FULL_PREFIX, is_thinking_status_text, thinking_status_round,\n    };\n\n    use super::{MatrixStreamMode, markers};\n\n    const MULTI_MESSAGE_SYNTHETIC_PREFIX: &str = \"multi_message_synthetic:\";\n\n    #[derive(Debug, Clone, PartialEq, Eq, Hash)]\n    pub(super) struct DraftKey {\n        room_id: OwnedRoomId,\n        draft_id: String,\n    }\n\n    pub(super) fn draft_key(room_id: OwnedRoomId, draft_id: &str) -> Result<DraftKey> {\n        let draft_id = draft_id.trim();\n        if draft_id.is_empty() {\n            bail!(\"matrix: draft message id is empty\");\n        }\n        Ok(DraftKey {\n            room_id,\n            draft_id: draft_id.to_string(),\n        })\n    }\n\n    pub(super) fn new_multi_message_draft_id() -> String {\n        format!(\n            \"{MULTI_MESSAGE_SYNTHETIC_PREFIX}{}\",\n            uuid::Uuid::new_v4().simple()\n        )\n    }\n\n    #[derive(Debug, Clone)]\n    pub(super) struct PartialDraft {\n        pub event_id: OwnedEventId,\n        pub thread_anchor: Option<OwnedEventId>,","sourceCodeStart":379,"sourceCodeEnd":415,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/matrix.rs#L379-L415","documentation":"draft_key builds the (room_id, draft_id) key Matrix draft bookkeeping uses to correlate in-flight drafts with their source events across update, finalize and cancel. It trims the incoming draft id and rejects empty ids, because an empty key would make every draft in a room collide on lookup. Hitting it means a caller passed an id that is empty or whitespace-only - a caller contract violation, not a network or server problem.","triggerScenarios":"Calling draft_key(room_id, draft_id) with a draft id that is empty or only whitespace: the message-id field of an upstream Matrix event was blank, or adapter/test code constructed the DraftKey from unsanitized input.","commonSituations":"Forwarding event ids from malformed or synthetic Matrix events (dev bridges, fixtures with missing ids); refactors that pass a placeholder empty string; reading the wrong field of an event and defaulting it to empty.","solutions":["Trace the caller and confirm the source Matrix event actually carries a non-empty message id before draft_key runs.","Validate and reject empty ids at the ingestion boundary, before the draft path is entered.","If the id comes from user or config input, validate it at parse time rather than deep in draft bookkeeping."],"exampleFix":"// before\nlet key = draft_key(room_id, maybe_id.as_deref().unwrap_or(\"\"))?;\n\n// after\nlet raw = maybe_id.as_deref().map(str::trim).unwrap_or(\"\");\nif raw.is_empty() {\n    anyhow::bail!(\"upstream event for room {room_id} has no message id; skipping draft\");\n}\nlet key = draft_key(room_id, raw)?;","handlingStrategy":"validation","validationCode":"fn usable_draft_id(id: &str) -> bool {\n    !id.trim().is_empty()\n}\n\n// gate before building the key\nif !usable_draft_id(&event_id) {\n    return Ok(()); // skip draft bookkeeping for id-less events\n}","typeGuard":"fn non_empty_draft_id(id: &str) -> Option<&str> {\n    let trimmed = id.trim();\n    (!trimmed.is_empty()).then_some(trimmed)\n}","tryCatchPattern":null,"preventionTips":["Never default an event or message id to an empty string; make the field required at the parse boundary.","Skip draft bookkeeping entirely for events without ids instead of synthesizing keys.","In tests, always seed draft ids - fixtures with blank ids produce this exact bail."],"tags":["matrix","draft","validation","empty-id"],"backgroundTag":"empty-required-identifier","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}