block / block/buzz

`buzz messages edit` silently deletes the message's attachments

Open
#3,022 2 comments 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
32.7k
Forks
4.3k
Avg merge
1d 13h
Merged PRs (30d)
253

Description

## Summary

Editing a message from the CLI strips every attachment off it. `build_edit` emits only `h` and `e` tags, and the desktop's render/cache overlay reads an edit with zero `imeta` tags as "the new attachment set is empty." The CLI exits 0 and gives you no way to put them back.

Same data-loss bug PR #755 fixed for the desktop. The CLI was never brought along.

## Why it happens

`crates/buzz-sdk/src/builders.rs:377-389`:

```rust
pub fn build_edit(
channel_id: Uuid,
target_event_id: nostr::EventId,
new_content: &str,
) -> Result {
check_content(new_content, 64 * 1024)?;
let tags = vec![
tag(&["h", &channel_id.to_string()])?,
tag(&["e", &target_event_id.to_hex()])?,
];
Ok(EventBuilder::new(Kind::Custom(40003), new_content).tags(tags))
}
```

No media parameter, and no caller could supply one anyway. `buzz messages edit` takes `--event` and `--content` only (`crates/buzz-cli/src/lib.rs:413-423`) and reaches `build_edit` at `crates/buzz-cli/src/commands/messages.rs:713`.

The receiving side reads that absence as deletion. `desktop/src/features/messages/lib/applyEditTagOverlay.mjs:31-42`:

```js
export function applyEditTagOverlay(originalTags, editTags) {
if (!editTags) return originalTags;
const editEmoji = editTags.filter((t) => t[0] === "emoji");
const droppedFromOriginal =
editEmoji.length > 0
? (t) => t[0] !== "imeta" && t[0] !== "emoji"
: (t) => t[0] !== "imeta";
```

`imeta` is unconditionally dropped from the original and replaced by whatever the edit carries, which for a CLI edit is nothing. That behaviour is deliberate and pinned by name:

```js
test("edit with zero imeta tags strips all attachments; non-imeta original tags stay", ...)
```
`desktop/src/features/messages/lib/applyEditTagOverlay.test.mjs:52`

Compare the `emoji` branch two lines up: an edit that supplies no emoji tags *preserves* the original's, and the comment at `applyEditTagOverlay.mjs:16-25` explains why, that a tag-less edit "can come from an older build ... or another client that doesn't know this path." That reasoning applies to the CLI and `imeta` word for word. It just wasn't extended there.

The desktop's own builder does carry media:

```rust
pub fn build_message_edit(
channel_id: Uuid,
target_event_id: EventId,
content: &str,
media_tags: &[Vec],
custom_emoji_tags: &[Vec],
mentions: &[&str],
) -> Result {
```
`desktop/src-tauri/src/events.rs:410-417`, with `imeta_tags(media_tags, ...)` at `:425`.

## Impact

An agent fixing a typo in its own message deletes the screenshot it attached. `buzz` exits 0, nothing in the output mentions attachments, and there's no `buzz messages edit --media` to restore them. The only recovery is deleting the message and posting it again.

`VISION.md:163` says buzz-cli "mirrors and extends the MCP surface ... Agents can script the entire platform without a GUI," and `README.md:40` promises agents "the same affordances as a human teammate." A human editing in the desktop keeps their attachments. An agent editing over the CLI loses them.

## Suggested fix

Fix it at the builder, not at the overlay. The overlay's replace-on-`imeta` semantics is what makes attachment removal work in the desktop, and changing it there would break `applyEditTagOverlay.test.mjs:52` for a good reason.

1. Add an options struct, or a `media_tags: &[Vec]` parameter, to `build_edit` in `crates/buzz-sdk/src/builders.rs`, validating the `imeta` prefix the way `desktop/src-tauri/src/events.rs:96-108` does.
2. Add `--media` to `MessagesCmd::Edit` (`crates/buzz-cli/src/lib.rs:413`), matching whatever flag `buzz messages send` already uses for attachments.
3. The default is the real question. The safe default is for `buzz messages edit` with no `--media` to fetch the target event and carry its existing `imeta` forward, so today's invocations stop losing data, with an explicit `--no-media` to clear. The alternative, requiring `--media` explicitly and dropping otherwise, is easier to reason about but doesn't help anyone who isn't already paying attention. I'd go with carry-forward, but I can see the argument against putting a relay fetch in the CLI's synchronous edit path, so I'd rather agree on it before building.

The same argument applies more weakly to NIP-30 `emoji` tags, which `build_edit` also can't emit. Nothing is lost today because the overlay preserves those on empty, but it'd be worth adding in the same pass for symmetry.

`builders.rs:2171` and `:2182` already cover `build_edit` directly, so the tests have somewhere to go.

Happy to send the PR once you've picked a default.

Contributor guide

Open the contributing guide

Research direction

Start with build_edit in crates/buzz-sdk/src/builders.rs:377-389, the MessagesCmd::Edit definition in crates/buzz-cli/src/lib.rs:413-423, and its caller at crates/buzz-cli/src/commands/messages.rs:713. Review the existing attachment flag and builders.rs tests at :2171 and :2182, then agree on carry-forward versus explicit clearing; done means CLI edits no longer unintentionally remove imeta tags and the behavior is covered by tests.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, rust
Domain
cli
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.