The channel drops images, and drops their captions with them #106

Closed
opened 2026-08-25 01:38:44 +00:00 by jlxq0 · 0 comments
Owner

Three of Julian's messages never reached the agent tonight, each carrying a real instruction. They were found only by dumping the room and grepping for msgtype != m.text. He asked "is it possible that you are not receiving images?" and the answer was yes, silently, for hours.

Why nobody caught it: the two paths already disagree

The live path drops these events. Replay does not. read_events_from_chunk fills untrusted_body from content.body for any event that has one — so a caption dropped live reappears on the next attach, hours later, out of order, with nothing marking it as late.

That is why this presented as "Julian's messages are sometimes slow" rather than "images are dropped". A message that never arrives is noticed. A message that arrives late and unlabelled is explained away.

Where it happens

src/channel.rs:695 — the live push path:

let MessageType::Text(text) = &ev.content.msgtype else {
    info!( "channel: skipped, not an m.text message");
    return;
};

is_text_message at line 629 does the same for replay.

The reasoning is sound and the premise is wrong

The comment at channel.rs:622 gives the reason:

an image or file event also carries a body — the sender's chosen filename — and pushing that as if it were a message misrepresents what arrived.

That is true when there is no caption. It is false when there is one, and this repository already knows itsrc/mcp.rs:5245, on the send side:

Per Matrix spec: when filenamebody, body is the caption. When there is no caption, use the filename as body (no separate filename field) so clients show a file name.

So the answer to "is body a filename or a caption" exists in this codebase, 4,600 lines from the code that assumes the opposite. In Element on a phone, a photo with a caption puts the caption in content.body — which is the traffic that actually arrives, and it is being dropped as though it were a filename.

What is being asked for, and the part to push back on

Push image and file events too, with the body carried as a caption and the event marked as an attachment rather than a message, so the model can tell the difference between "somebody said this" and "somebody sent a file called this".

Downloading is already solved: download_attachment exists on /mcp and was used by hand to recover all three lost messages. Only the push path is missing.

The filename-versus-caption distinction is the actual design question, and the spec rule the send path already implements is the obvious answer: if filename is present and differs from body, body is a caption and belongs in the channel; if it is absent, body is a filename and should be labelled as one rather than pushed as prose.

The team lead should push back if there is a better shape — in particular on whether an attachment event should carry the mxc:// so the model can fetch it, and on what the content sandbox does with a caption, since a caption is untrusted input from the same sender as any other message.

**Three of Julian's messages never reached the agent tonight, each carrying a real instruction.** They were found only by dumping the room and grepping for `msgtype != m.text`. He asked *"is it possible that you are not receiving images?"* and the answer was yes, silently, for hours. ## Why nobody caught it: the two paths already disagree The live path drops these events. **Replay does not.** `read_events_from_chunk` fills `untrusted_body` from `content.body` for *any* event that has one — so a caption dropped live reappears on the next attach, hours later, out of order, with nothing marking it as late. That is why this presented as *"Julian's messages are sometimes slow"* rather than *"images are dropped"*. A message that never arrives is noticed. A message that arrives late and unlabelled is explained away. ## Where it happens `src/channel.rs:695` — the live push path: ```rust let MessageType::Text(text) = &ev.content.msgtype else { info!(… "channel: skipped, not an m.text message"); return; }; ``` `is_text_message` at line 629 does the same for replay. ## The reasoning is sound and the premise is wrong The comment at `channel.rs:622` gives the reason: > an image or file event also carries a `body` — the sender's chosen filename — and pushing that as if it were a message misrepresents what arrived. That is true when there is no caption. **It is false when there is one, and this repository already knows it** — `src/mcp.rs:5245`, on the send side: > Per Matrix spec: when `filename` ≠ `body`, `body` is the caption. When there is no caption, use the filename as `body` (no separate `filename` field) so clients show a file name. So the answer to "is `body` a filename or a caption" exists in this codebase, 4,600 lines from the code that assumes the opposite. **In Element on a phone, a photo with a caption puts the caption in `content.body`** — which is the traffic that actually arrives, and it is being dropped as though it were a filename. ## What is being asked for, and the part to push back on Push image and file events too, with the body carried as a caption and the event marked as an **attachment** rather than a message, so the model can tell the difference between "somebody said this" and "somebody sent a file called this". Downloading is already solved: `download_attachment` exists on `/mcp` and was used by hand to recover all three lost messages. **Only the push path is missing.** **The filename-versus-caption distinction is the actual design question**, and the spec rule the send path already implements is the obvious answer: if `filename` is present and differs from `body`, `body` is a caption and belongs in the channel; if it is absent, `body` is a filename and should be labelled as one rather than pushed as prose. The team lead should push back if there is a better shape — in particular on whether an attachment event should carry the `mxc://` so the model can fetch it, and on what the content sandbox does with a caption, since a caption is untrusted input from the same sender as any other message.
jlxq0 closed this issue 2026-08-25 02:13:29 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
jlxq0/matrix-mcp#106
No description provided.