Skip to content

fix(cli): preserve attachments on buzz messages edit (#3022) - #3065

Open
maikunari wants to merge 1 commit into
block:mainfrom
maikunari:fix/cli-edit-preserve-attachments
Open

maikunari wants to merge 1 commit into
block:mainfrom
maikunari:fix/cli-edit-preserve-attachments

Conversation

@maikunari

Copy link
Copy Markdown

Problem

buzz messages edit silently deletes a message's attachments. build_edit (crates/buzz-sdk/src/builders.rs) emits only h + e tags — never imeta. The desktop render/cache overlay (desktop/src/features/messages/lib/applyEditTagOverlay.mjs) treats an edit carrying zero imeta tags as "the attachment set is now empty" — this is intentional (it's how deliberate attachment removal works), and is pinned by a test. Because the CLI never carried imeta forward, an agent fixing a typo in its own message deletes the screenshot it attached. buzz exits 0, nothing warns, and there's no flag to restore them.

Desktop was fixed for its own builder in #755; the CLI was never brought along. Closes #3022.

Fix

Fixed at the builder + CLI, not the overlay (the overlay's replace-on-imeta semantics is correct and load-bearing for desktop removal).

  • build_edit gains a media_tags: &[Vec<String>] parameter and emits imeta via the same imeta_tags() helper build_message already uses (same validation path).
  • buzz messages edit gains --file and --no-media:
    • (neither flag — default) carry-forward: fetch the target event and preserve its existing imeta tags, so today's invocations stop losing attachments.
    • --file <path> (repeatable): upload and replace attachments — identical upload path to buzz messages send (this is why the flag is --file, matching send, not --media).
    • --no-media: clear all attachments (explicit). Mutually exclusive with --file (enforced by clap).

The single relay fetch already performed to resolve the channel id is reused to read the carry-forward imeta — no extra round-trip. A failed/absent fetch aborts before signing, so an attachment-wiping edit can never be published on error.

Tests

  • buzz-sdk: edit_with_media (imeta emitted), edit_without_media_has_no_imeta (clear contract), edit_rejects_malformed_media_tag.
  • buzz-cli: extract_imeta_tags_returns_only_imeta / _empty_when_none / _handles_missing_tags (only imeta carried, full vectors preserved), and a clap test that --file + --no-media is rejected.
cargo test -p buzz-sdk     # 238 passed
cargo test -p buzz-cli     # 254 passed
cargo fmt --all -- --check # clean
cargo clippy -p buzz-sdk -p buzz-cli -- -D warnings  # clean

Known follow-ups (non-blocking)

  • No automated end-to-end test for the full fetch→extract→build path — the unit tests cover the pieces; the full flow is covered by the manual runbook in crates/buzz-cli/TESTING.md (no mocked-relay harness exists for CLI command handlers). Verified manually.
  • Inline ![image](url) markdown in the old body is not carried forward — --content replaces the body wholesale. The imeta attachment (what desktop renders) is preserved; only inline markdown in a plaintext client would be affected. Left as-is to keep the change surgical.

@maikunari
maikunari requested a review from a team as a code owner July 27, 2026 03:27
@maikunari
maikunari force-pushed the fix/cli-edit-preserve-attachments branch from e35d234 to 2b733a8 Compare July 31, 2026 04:07
buzz messages edit stripped every attachment off a message. build_edit
emitted only h + e tags, and the desktop render/cache overlay reads an
edit with zero imeta tags as "attachments cleared" (intentional, so
deliberate removal works). The CLI never carried imeta forward, so an
agent fixing a typo silently deleted its own screenshots. Desktop was
fixed in block#755; the CLI was never brought along.

Fix at the builder + CLI, not the overlay:
- build_edit (buzz-sdk) gains a media_tags parameter and emits imeta via
  the same imeta_tags() helper build_message uses.
- buzz messages edit gains --file (replace attachments, uploaded like
  messages send) and --no-media (clear). With neither flag it fetches the
  target event and carries its existing imeta tags forward, so edits stop
  losing attachments by default.

The single event fetch already done to resolve the channel id is reused
to read the carry-forward imeta, so no extra relay round-trip. A failed
fetch aborts before signing, so an attachment-wiping edit can never be
published on error.

Closes block#3022

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Signed-off-by: Mike Sewell <[email protected]>
@maikunari
maikunari force-pushed the fix/cli-edit-preserve-attachments branch from 2b733a8 to 3e90361 Compare August 7, 2026 09:04
@maikunari

Copy link
Copy Markdown
Author

Rebased onto latest main (f53bbd115).

  • Conflict was only in crates/buzz-cli/src/lib.rs (new projects clap tests on main vs our edit-flag clap test) — kept both.
  • Local verification on rebased tip: cargo test -p buzz-sdk (257 passed) and cargo test -p buzz-cli (325 passed).
  • Branch is now up to date / should be mergeable pending review.

vs #3062: same issue (#3022). This PR stays the surgical path — fix at build_edit + CLI carry-forward default, with explicit --file (replace) and --no-media (clear). Happy to close as duplicate if maintainers prefer #3062's approach once one lands.

Re-requesting review from @block/buzz-oss-team.

bradseiler added a commit that referenced this pull request Nov 8, 2026
## Summary

Editing attachment messages through `buzz messages edit` can remove
their files from the effective message. Preserve the latest live imeta
set across successive edits and add `--clear-attachments` for explicit
removal. Add an SDK builder for complete replacement attachment sets
while preserving the existing API.

The relay provides a versioned effective-edit lookup on `/query` for
kinds 9/40002/40008/45001/45003; media reads fold the same set. It uses
the existing normalized-target index, fresh channel authorization, and a
primary snapshot. Generic `#e` matching is unchanged. Mixed-case
historical edits cannot restore removed files, including when another
reference would allow republication.

### Related issue

Fixes #3022. Follows #9077. Related open attempts: #3062 and #3065.

### Testing

Published head: `c630c4ebf0b20a9618f4844629eec1e666699cbe`, through all
mandatory push hooks. The governed-five snapshot passed all four
PostgreSQL/HTTP acceptance tests with `RUST_MIN_STACK` unset and 3,333
affected-package tests. The committed test-only array correction passed
clippy and the preflight rerun.

Independent mutations detected dropped read/lookup kinds, the CLI
two-kind split, and preflight ordering, channel and live-edit errors.
The published follow-up adds forum preview retention/suppression and a
deleted-original preflight fixture; both surviving mutations now fail as
intended. Independent review verified the fixes. All four acceptance
tests pass; the full DB/relay run had two NIP-FI failures that passed in
isolation. The actual standalone MCP workflow passed against this relay
and clean local app `5b583b8c69596353bc37e9f3938eca1aabcd081e`: 15 MCP
edits, 10 direct publications and 60 member GET/HEAD checks across five
isolated blob pairs. Both tested heads are now published. Relay hosted
checks report 80 successful and 31 skipped, with none pending or failed;
app hosted checks and human upload → edit → clear → edit → GET/HEAD
confirmation remain pending. No human acceptance is claimed.

### Rollout

The lookup, CLI preservation/clear and media reads use kinds
9/40002/40008/45001/45003. Deploy the relay before releasing the CLI and
paired [MCP producer](block/buzz-app#2937). New
clients abort without publishing against an unsupported relay. Old CLI
edits emit empty imeta and now revoke diff/forum attachments that
previously remained readable. Other channel-tagged kinds retain the
historical plain-edit path. No additional schema change is needed beyond
migration 0086.

Readiness remains blocked by mobile edits that omit imeta, the app UI
fold that can restore older files, unaudited SDK/direct callers and
unmeasured historical exposure. Run
`docs/sql/private-media-edit-preflight.sql` for each community; producer
updates do not repair signed historical edits. No enforce activation is
authorized. `off` skips policy checks, `shadow` changes policy metrics
while allowing policy failures, and `enforce` denies inaccessible
references.

Observe
`buzz_media_publication_access_total{mode="shadow",reason="denied"}`.
Prefer rolling back the client release while retaining the additive
lookup. A relay rollback makes new CLI/MCP edits fail closed;
already-published imeta remains valid under the older reference rules.
Concurrent edits still use `(created_at,id)` without compare-and-swap.

Generated with Codex

---------

Signed-off-by: Brad Seiler <[email protected]>
Co-authored-by: Honor <e0bf479eeebe47dde1e35705212a4df70616a7bc834d907ddfb18cf0405f8b1b@seiler-test.communities.public.builderlabstaging.xyz>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

buzz messages edit silently deletes the message's attachments

1 participant