Skip to content

fix(cli): read social publish body from stdin for the - sentinel - #4931

Open
Pratikkale26 wants to merge 1 commit into
block:mainfrom
Pratikkale26:fix/social-publish-stdin-4905
Open

fix(cli): read social publish body from stdin for the - sentinel#4931
Pratikkale26 wants to merge 1 commit into
block:mainfrom
Pratikkale26:fix/social-publish-stdin-4905

Conversation

@Pratikkale26

Copy link
Copy Markdown

Summary

buzz social publish --content - returned accepted:true and published a literal one-character - note, discarding the piped body. - is the stdin sentinel that messages send, notes set and mem set all accept — social publish just never implemented it.

Fixes #4905.

Reproduced

Before, against a local relay:

$ printf '%s' 'expected multiline note body' | buzz social publish --content -
{"accepted":true,"event_id":"…"}

$ # readback
content: "-"

After:

$ printf '%s' 'expected multiline note body\nsecond line' | buzz social publish --content -
{"accepted":true,"event_id":"960eb0e2…"}

$ select content from events where kind = 1 order by created_at desc limit 1;
expected multiline note body
second line

It's a silent-success failure in an awkward place: the caller is told the publish worked, the intended body is gone, and buzz social has no delete command to walk a kind:1 note back.

The fix

cmd_publish_note passed content straight to build_note. It now resolves the sentinel — but using the hygiene the other publish-shaped commands apply, rather than the bare read_or_stdin:

  • capped read (1 MiB, matching notes set) so a runaway producer can't OOM the CLI — no Nostr kind caps event size for us
  • empty reads refused unless --allow-empty, because an empty pipe almost always means an upstream pipeline step failed, and publishing the empty result is precisely the outcome this issue is about

Values other than - are still returned verbatim, including an explicit empty string — the guard applies to stdin reads only, so it never second-guesses a body the caller passed directly.

Where the logic lives

notes set and mem set each carry their own inline copy of this. Rather than add a third, it now lives in validate::read_stdin_sentinel. This PR deliberately does not refactor those two working call sites onto it — happy to follow up separately if that's wanted.

The reader half is split into read_sentinel_from(impl Read, …) so the cap and the empty-body guard are testable without a process-global stdin. That's why the existing read_or_stdin tests only ever covered its passthrough branch — there was no way to exercise the read.

Not addressed

The issue also notes there's no CLI path to delete the resulting bad note (buzz social has no note/event delete command). That's a new command surface rather than a bug fix, so it isn't in this PR. Happy to add social delete as a follow-up if maintainers want it — it'd be a kind:5 e-tag deletion.

Testing

Five new unit tests in validate.rs:

Test Asserts
read_stdin_sentinel_passes_literal_values_through_untouched only - means stdin; -x and a - b are bodies
read_stdin_sentinel_passes_empty_literal_through_without_the_guard an explicit "" is the caller's choice
read_sentinel_from_reads_the_whole_body multiline bodies survive intact
read_sentinel_from_rejects_empty_unless_allowed empty refused, and the error names --allow-empty
read_sentinel_from_enforces_the_byte_cap at-cap passes, one byte over is refused rather than silently truncated

Also verified end-to-end against a local relay: piped multiline content lands intact, empty stdin is refused, --allow-empty opts in, and literal --content is unaffected.

cargo fmt --all -- --check                             ✅
cargo clippy --workspace --all-targets -- -D warnings   ✅
cargo test -p buzz-cli                                  ✅ 326 passed
just test                                               ✅ 11/11 suites

Related

Searched open PRs for #4905 and for social/stdin work — none found.

`buzz social publish --content -` returned `accepted:true` and published a
literal one-character `-` note, discarding the piped body. `-` is the
stdin sentinel `messages send`, `notes set` and `mem set` all accept, but
`cmd_publish_note` passed `content` straight to `build_note`.

It is a silent-success failure in the worst place: the caller is told the
publish worked, the intended body is gone, and `buzz social` has no delete
command to walk a kind:1 note back.

Resolve the sentinel with the hygiene the other publish-shaped commands
use rather than the bare `read_or_stdin`:

- the read is capped (1 MiB, matching `notes set`) so a runaway producer
  cannot OOM the CLI — no Nostr kind caps event size for us; and
- an empty read is refused unless `--allow-empty`, because an empty pipe
  almost always means an upstream pipeline step failed, and publishing the
  empty result is exactly the outcome this issue is about.

That logic now lives in `validate::read_stdin_sentinel` instead of a third
inline copy. `notes set` and `mem set` could adopt it in a follow-up; this
change deliberately leaves those working call sites alone.

Values other than `-` are still returned verbatim, including an explicit
empty string: the guard applies to stdin reads only, so it never
second-guesses a body the caller passed directly.

The reader half is split into `read_sentinel_from` so the cap and the
empty-body guard are testable without a process-global stdin, which is why
the existing `read_or_stdin` tests only ever covered its passthrough
branch.

The issue also notes there is no CLI path to delete the resulting bad note.
That is a new command surface rather than a bug fix and is not addressed
here.

Fixes block#4905

Signed-off-by: pratikkale26 <pratikkale7661@gmail.com>
@Pratikkale26
Pratikkale26 requested a review from a team as a code owner August 5, 2026 18:20
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 social publish treats --content - literally, returns accepted, and cannot delete the bad note

1 participant