You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Reported on Matrix (2026-07-21) by @ofansifkapital-xmpp and confirmed by @Ivvvor: message edits appear to be silently dropped. Both reporters attributed it to encrypted (private) rooms; the measurements below suggest the real variable is edit length, which is privacy-independent apart from 16 bytes.
An edit action costs ~2.1 bytes per ASCII character, while a plain message costs ~1.01. With the default max_message_size = 1000, that means:
effective ceiling
send a message
~985 chars
edit a message
~467 chars (public) / ~459 chars (private)
Consequence: any message longer than ~467 characters can be sent but can never be edited. Before #431 there was no edit-size gate at all, so attempting it produced exactly the reported symptom — click Save, form closes, text reverts, nothing on the wire, nothing in the console. After #431 it fails loudly (Message too long — 1006/1000 bytes, Save disabled), but it still cannot be done.
Root cause
ActionContentV1::payload is a bare Vec<u8> (common/src/room_state/content.rs:82-89):
serde has no distinct byte-string type in the derive path, so ciborium serialises Vec<u8> as a CBOR array of integers. Every byte >= 0x18 costs 2 bytes, and all printable ASCII is >= 0x20. TextContentV1 { text: String } becomes a CBOR text string, so plain messages don't pay this — hence the asymmetry.
The edit text is also double-encoded: EditPayload { new_text } is CBOR'd into payload, then payload is re-encoded as an array of ints.
Verification
Independent reproduction of the encoding (standalone ciborium program mirroring the structs):
Cross-checked against the live published UI's own byte counter (build 2026-07-21T19:35:51Z, commit f3f6907): a 900-char edit measured 1866 bytes in a public room and 1882 in a private room — the 16-byte delta is exactly the AES-GCM tag. A 470-char edit measured 1006/1000 with Save disabled. These match the standalone numbers within a few bytes.
Suggested fix
#[serde(with = "serde_bytes")] pub payload: Vec<u8> would roughly halve the cost and bring the edit ceiling in line with the send ceiling.
This is a wire-format change — it alters ActionContentV1's CBOR encoding, so it changes the room-contract WASM and needs the room-contract + delegate migration ritual, plus a compatibility story for edit/reaction actions already stored in existing rooms under the old encoding. Flagging rather than implementing; wants a decision on the migration approach.
A non-breaking stopgap would be to raise max_message_size, but that doesn't fix the class (per #431's own reasoning) and leaves the send/edit asymmetry in place.
What this does NOT explain
Both reporters framed this as private-room-specific. The doubling happens in ActionContentV1::encode()before encryption, so public rooms are equally affected. I could not reproduce a privacy-specific edit failure on the current build: short edits in private rooms worked in every scenario tried (owner edit, edit across sessions, re-edit, edit after a new message, invitee-on-a-different-node edit, and live cross-member propagation).
Whether the reporters' edits were actually long. Worth asking them for the length of the messages they tried to edit, and whether those rooms have had members removed.
Problem
Reported on Matrix (2026-07-21) by @ofansifkapital-xmpp and confirmed by @Ivvvor: message edits appear to be silently dropped. Both reporters attributed it to encrypted (private) rooms; the measurements below suggest the real variable is edit length, which is privacy-independent apart from 16 bytes.
An edit action costs ~2.1 bytes per ASCII character, while a plain message costs ~1.01. With the default
max_message_size = 1000, that means:Consequence: any message longer than ~467 characters can be sent but can never be edited. Before #431 there was no edit-size gate at all, so attempting it produced exactly the reported symptom — click Save, form closes, text reverts, nothing on the wire, nothing in the console. After #431 it fails loudly (
Message too long — 1006/1000 bytes, Save disabled), but it still cannot be done.Root cause
ActionContentV1::payloadis a bareVec<u8>(common/src/room_state/content.rs:82-89):serde has no distinct byte-string type in the derive path, so ciborium serialises
Vec<u8>as a CBOR array of integers. Every byte >= 0x18 costs 2 bytes, and all printable ASCII is >= 0x20.TextContentV1 { text: String }becomes a CBOR text string, so plain messages don't pay this — hence the asymmetry.The edit text is also double-encoded:
EditPayload { new_text }is CBOR'd intopayload, thenpayloadis re-encoded as an array of ints.Verification
Independent reproduction of the encoding (standalone ciborium program mirroring the structs):
Cross-checked against the live published UI's own byte counter (build
2026-07-21T19:35:51Z, commit f3f6907): a 900-char edit measured 1866 bytes in a public room and 1882 in a private room — the 16-byte delta is exactly the AES-GCM tag. A 470-char edit measured 1006/1000 with Save disabled. These match the standalone numbers within a few bytes.Suggested fix
#[serde(with = "serde_bytes")] pub payload: Vec<u8>would roughly halve the cost and bring the edit ceiling in line with the send ceiling.This is a wire-format change — it alters
ActionContentV1's CBOR encoding, so it changes the room-contract WASM and needs the room-contract + delegate migration ritual, plus a compatibility story for edit/reaction actions already stored in existing rooms under the old encoding. Flagging rather than implementing; wants a decision on the migration approach.A non-breaking stopgap would be to raise
max_message_size, but that doesn't fix the class (per #431's own reasoning) and leaves the send/edit asymmetry in place.What this does NOT explain
Both reporters framed this as private-room-specific. The doubling happens in
ActionContentV1::encode()before encryption, so public rooms are equally affected. I could not reproduce a privacy-specific edit failure on the current build: short edits in private rooms worked in every scenario tried (owner edit, edit across sessions, re-edit, edit after a new message, invitee-on-a-different-node edit, and live cross-member propagation).Two gaps remain open:
secret_version: 0. A room that has had a member banned (which triggers rotation) is untested, as is a member who joined after a rotation. Note thathandle_edit_messagesilentlyreturns when no current-version secret is available (ui/src/components/conversation.rs:1544-1547) — that path is private-room-specific and would look identical to this bug. Related: UI: refresh secrets state before encrypting outbound private messages #271, Late joiners cannot decrypt messages after a secret rotation while owner is offline #309.Related
[AI-assisted - Claude]