Skip to content

fix(messages): reject blank message bodies at every write layer - #5505

Open
Dinehub1 wants to merge 1 commit into
block:mainfrom
Dinehub1:fix/blank-agent-messages
Open

fix(messages): reject blank message bodies at every write layer#5505
Dinehub1 wants to merge 1 commit into
block:mainfrom
Dinehub1:fix/blank-agent-messages

Conversation

@Dinehub1

Copy link
Copy Markdown

Problem

An empty message signs, publishes, and is accepted like any other message. check_content enforces only a ceiling, never a floor, so "" passes every layer:

  • buzz-sdk: build_message / build_forum_post / build_forum_comment / build_edit only bound the max size
  • buzz-cli: cmd_send_message calls validate_content_size, which has an explicit test asserting "" is valid
  • buzz-relay: ingest has no empty-content rejection for message kinds

The caller gets accepted: true and exit code 0 for a message that says nothing. Readers get a row with an author and a timestamp and no body — indistinguishable from a delivery failure.

How it happens in practice

Agent shells run every command with stdin on /dev/null (buzz-dev-mcp/src/shell.rs:175). The base prompt recommends printf '…' | buzz messages send … --content - for multiline bodies. Drop the pipe and --content - reads from a closed stdin, yields "", and silently publishes a blank message while reporting success — so the agent believes it spoke and moves on.

Observed repeatedly with a managed agent: the model produced output (539, 381, then 1290, 616 output tokens across two turns), the turn ended without error, and the published message was empty.

Fix

Guard all three layers, mirroring the content.trim().is_empty() check build_git_patch already had:

  • buzz-sdkcheck_content_not_blank in build_message (kind:9), build_forum_post (45001), build_forum_comment (45003), and build_edit (40003). Catches every caller, not just the CLI.
  • buzz-cli — reject in cmd_send_message via the pure, testable blank_content_message helper. The error names the actual cause, and the stdin case says so explicitly, so callers get exit 1 and can retry instead of silently "succeeding".
  • buzz-relay — reject blank kinds 9 / 40002 / 40003 / 45001 / 45003 at ingest, the one chokepoint every client shares.

Caption-less attachments stay legal throughout: a body whose payload is an imeta-tagged image is not blank. That exemption is load-bearing for edits specifically — dropping a caption while keeping the image arrives as empty content plus an imeta overlay.

validate_content_size is deliberately left accepting "". It is a size-only helper shared with paths where empty content is legitimate; the floor belongs at the message layer.

Testing

15 new tests: empty, whitespace-only (" ", "\n", "\t\n \r\n"), media-exempt, per-kind coverage, and for the CLI that the stdin message names stdin while the argv message does not.

buzz-sdk    263 passed
buzz-cli    346 passed
buzz-acp    736 passed
buzz-relay  873 passed

cargo fmt --all --check and cargo clippy --all-targets clean. Full pre-push gate green.

Verified end to end against the rebuilt binary, through the multicall shim the agent actually uses:

Invocation Result
--content - with stdin on /dev/null stdin error, exit 1
--content "" empty-content error, exit 1
--content " " rejected, exit 1
printf 'hello\n' | … --content - passes the guard, reaches the network

One pre-existing unrelated failure, api::mesh_demo::demo_join_forwarded_arm_round_trips_echo, reproduces identically on a clean main.

🤖 Generated with Claude Code

An empty message signed, published, and was accepted like any other
message: `check_content` enforced only a ceiling, never a floor. The
caller got `accepted: true` and exit code 0 for a message that said
nothing, and readers got a row with an author and a timestamp and no
body -- indistinguishable from a delivery failure.

The path that produced it in practice: agent shells run every command
with stdin on /dev/null (buzz-dev-mcp shell.rs), so a `--content -`
that lost its upstream pipe read "" rather than erroring.

Guard all three layers:

- buzz-sdk: `check_content_not_blank` in build_message (kind:9),
  build_forum_post (45001), build_forum_comment (45003), and
  build_edit (40003), mirroring the guard build_git_patch already
  had. Catches every caller, not just the CLI.
- buzz-cli: reject in cmd_send_message with an error that names the
  cause, so the stdin case says so and the caller gets exit 1 and can
  retry instead of silently "succeeding".
- buzz-relay: reject blank kinds 9/40002/40003/45001/45003 at ingest,
  the one chokepoint every client shares.

Caption-less attachments stay legal throughout: a body whose payload
is an imeta-tagged image is not blank. That exemption is load-bearing
for edits, where dropping a caption but keeping the image arrives as
empty content plus an imeta overlay.

Signed-off-by: Brand Collabs <brandcollabs1@gmail.com>
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.

1 participant