You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
An @mention written with markdown emphasis — **@Name**, *@Name*, _@Name_ — is silently dropped by buzz messages send. The message posts normally, the CLI exits 0 with no warning, and the event carries zero p tags, so nobody is notified and no buzz-acp agent wakes.
This affects the CLI/SDK path only. The Desktop composer is not affected — its TypeScript parser already handles emphasis (see "Why Desktop is fine" below). The impact lands on agents, which post exclusively through buzz messages send, and on anyone scripting the CLI.
To Reproduce
buzz messages send --channel <uuid> --content '**@Alice** please take a look'# exit 0, message appears in the channel, zero p tags on the event
buzz messages send --channel <uuid> --content '@Alice please take a look'# p tag for Alice present
Expected behavior
**@Alice** should resolve exactly like @Alice. Failing that, the CLI should warn when the content contains @ tokens but zero mentions resolved, rather than exiting 0 silently.
Observed in production
Three consecutive messages from one agent, same channel, same command, same intended recipients. The only difference is the **:
event
mention as written
p tags on the event
d2c755cc
… directly. @Atlas that assumption …
a8a3fd71, 3f770d65
9a0c6e2c
**@Atlas** your fix is aimed … / **@Derek** so item 1 …
none at all
f9402f75
**@Atlas** … plus one bare @Atlas inside a table
a8a3fd71 only — the bare one
Pulled from the relay with buzz messages get --channel <uuid>; 9a0c6e2c has an empty p set. Two agents in that thread went unnotified and the handoff stalled until a human noticed.
Root cause
crates/buzz-sdk/src/mentions.rs:123 — extract_at_mentions_with_known only accepts an @ at start-of-string or preceded by ASCII whitespace:
let preceded = i == 0 || content.as_bytes()[i - 1].is_ascii_whitespace();if !preceded {continue;}
In **@Atlas the preceding byte is *, so the mention is skipped. extract_at_names carries the identical rule at mentions.rs:75.
The call site is resolve_content_mentions at crates/buzz-cli/src/commands/messages.rs:191. It returns whatever the parser found and never reports an empty result — hence exit 0, no warning.
The whitespace rule is deliberate: it stops user@host in an email address from becoming a mention (documented at mentions.rs:57-59). Markdown emphasis is collateral damage.
Why Desktop is fine — and why that is the actual defect
Desktop has a second, independent implementation of the same rule, and it was fixed there over three months ago. desktop/src/features/messages/lib/hasMention.ts:143-150:
The leading group explicitly admits */_ ×1–3, (, and ||; the trailing lookahead admits * and _ as well. Added in 0f93ef44 ("fix: mentions survive copy/paste from chat into composer", #328, 2026-04-15) — TipTap's Bold extension wraps pasted mentions in **, which broke them in exactly this way. That commit touched only desktop/. The Rust parser never got the equivalent.
So the defect is parser divergence: two implementations of "is this an @mention" giving different answers, on the two surfaces that have to agree.
Two further divergences in the same pair — worth fixing together
Trailing emphasis breaks multi-word display names. Rust's is_word_boundary (mentions.rs:154-157) accepts whitespace and ,;.!?:)]} — not *, _, or |. For a member named Will Pfleger, @Will Pfleger** fails the known-name match, falls through to the single-word tokenizer, and yields will, which matches nobody. The TS lookahead accepts *_ and handles this case.
The Rust path tags mentions inside code blocks; TS does not.strip_code_regions is applied only to NIP-27 URI extraction (crates/buzz-cli/src/commands/messages.rs:534), while @name extraction at line 191 receives the raw content. TS masks fenced blocks, indented blocks and backtick spans before matching (maskMarkdownCode in hasMention.ts). So an @name appearing inside a code sample sent from the CLI fires a real notification — a false positive mirroring the false negative above.
Suggested direction (a direction, not a prescription)
Bring mentions.rs to behavioural parity with hasMention.ts rather than patching the one symptom:
accept *, _, (, || as leading delimiters at mentions.rs:75 and mentions.rs:123;
widen is_word_boundary (mentions.rs:154) to match, so trailing emphasis stops breaking multi-word names;
apply strip_code_regions before @name extraction, not only before URI extraction.
Do not port the TypeScript regex into Rust. That would create a second hand-maintained delimiter list that drifts from the first exactly as the first drifted from the parser — which is this bug. Prefer widening the two existing Rust predicates against one shared "is this a delimiter" definition, and let the tests pin the behaviour. (Direction refined by Prometheus in triage; I originally suggested mirroring the regex and that was the worse call.)
Add Rust unit tests for the emphasis / spoiler / code cases. There are 51 tests in crates/buzz-sdk/src/mentions.rs (module at line 390+) and 18 more in crates/buzz-cli/src/commands/messages.rs (line 877+) — and not one of the 69 contains an emphasis character next to an @ (grep -n '\*\*@\|\*@\|_@' crates/buzz-sdk/src/mentions.rs → no matches). That gap is why the divergence survived a release.
Longer term the two parsers should be one shared implementation, or the divergence recurs.
A cheap independent mitigation, valuable on its own even if the parser is fixed: have buzz messages send warn on stderr when the content contains @ tokens but zero mentions resolved. Silent zero-recipient sends are the part that costs real time.
Related
[Bug] Handoffs between agents don't always work #2442 — handoffs between agents don't always work. Different root cause (the agent posts nothing at all), but this is a second, independent way an agent handoff fails with no signal to anyone.
Does #2319 / NIP-27 make this moot? No. Checked at origin/main = acfbb1bb:
fix(desktop): community create handoff, NIP-27 mentions, workflows list #2503 ("fix(desktop): community create handoff, NIP-27 mentions, workflows list") is the open PR carrying the NIP-27 work. All 15 changed files are under desktop/ — nip27Mentions.ts, rewriteMentionsToNip27.ts, collectMentionPubkeys.ts, hasMention.ts, MessageComposer.tsx, useMentionSendFlow.ts and friends. Zero files under crates/. Same shape as 0f93ef44: the TS side moves, the Rust side does not.
The SDK can read NIP-27 — extract_nostr_uris at mentions.rs:353 — but there is no write path: nothing in crates/ rewrites an @name into a nostr:npub1… ref.
So after #2503 lands, buzz messages send still emits bare @name text and still resolves it through extract_at_mentions_with_known. The text parser stays load-bearing on the agent path specifically, which is the exact path this bug breaks. Waiting for the NIP-27 work does not save the work.
(Verified by Prometheus and independently re-checked during triage.)
Environment
block/buzz @ acfbb1bb (main). Originally traced at f8fd055b; crates/buzz-sdk/src/mentions.rs and crates/buzz-cli/src/commands/messages.rs are byte-identical between the two, so every line number above is current.
Linux; buzz-cli as invoked by buzz-acp managed agents
Reported by Prometheus in Buzz #ditto, 2026-07-23. Triaged by Muse.
Describe the bug
An
@mentionwritten with markdown emphasis —**@Name**,*@Name*,_@Name_— is silently dropped bybuzz messages send. The message posts normally, the CLI exits 0 with no warning, and the event carries zeroptags, so nobody is notified and no buzz-acp agent wakes.This affects the CLI/SDK path only. The Desktop composer is not affected — its TypeScript parser already handles emphasis (see "Why Desktop is fine" below). The impact lands on agents, which post exclusively through
buzz messages send, and on anyone scripting the CLI.To Reproduce
Expected behavior
**@Alice**should resolve exactly like@Alice. Failing that, the CLI should warn when the content contains@tokens but zero mentions resolved, rather than exiting 0 silently.Observed in production
Three consecutive messages from one agent, same channel, same command, same intended recipients. The only difference is the
**:ptags on the eventd2c755cc… directly. @Atlas that assumption …a8a3fd71,3f770d659a0c6e2c**@Atlas** your fix is aimed …/**@Derek** so item 1 …f9402f75**@Atlas** …plus one bare@Atlasinside a tablea8a3fd71only — the bare onePulled from the relay with
buzz messages get --channel <uuid>;9a0c6e2chas an emptypset. Two agents in that thread went unnotified and the handoff stalled until a human noticed.Root cause
crates/buzz-sdk/src/mentions.rs:123—extract_at_mentions_with_knownonly accepts an@at start-of-string or preceded by ASCII whitespace:In
**@Atlasthe preceding byte is*, so the mention is skipped.extract_at_namescarries the identical rule atmentions.rs:75.The call site is
resolve_content_mentionsatcrates/buzz-cli/src/commands/messages.rs:191. It returns whatever the parser found and never reports an empty result — hence exit 0, no warning.The whitespace rule is deliberate: it stops
user@hostin an email address from becoming a mention (documented atmentions.rs:57-59). Markdown emphasis is collateral damage.Why Desktop is fine — and why that is the actual defect
Desktop has a second, independent implementation of the same rule, and it was fixed there over three months ago.
desktop/src/features/messages/lib/hasMention.ts:143-150:The leading group explicitly admits
*/_×1–3,(, and||; the trailing lookahead admits*and_as well. Added in0f93ef44("fix: mentions survive copy/paste from chat into composer", #328, 2026-04-15) — TipTap's Bold extension wraps pasted mentions in**, which broke them in exactly this way. That commit touched onlydesktop/. The Rust parser never got the equivalent.So the defect is parser divergence: two implementations of "is this an @mention" giving different answers, on the two surfaces that have to agree.
Two further divergences in the same pair — worth fixing together
Trailing emphasis breaks multi-word display names. Rust's
is_word_boundary(mentions.rs:154-157) accepts whitespace and,;.!?:)]}— not*,_, or|. For a member namedWill Pfleger,@Will Pfleger**fails the known-name match, falls through to the single-word tokenizer, and yieldswill, which matches nobody. The TS lookahead accepts*_and handles this case.The Rust path tags mentions inside code blocks; TS does not.
strip_code_regionsis applied only to NIP-27 URI extraction (crates/buzz-cli/src/commands/messages.rs:534), while@nameextraction at line 191 receives the raw content. TS masks fenced blocks, indented blocks and backtick spans before matching (maskMarkdownCodeinhasMention.ts). So an@nameappearing inside a code sample sent from the CLI fires a real notification — a false positive mirroring the false negative above.Suggested direction (a direction, not a prescription)
Bring
mentions.rsto behavioural parity withhasMention.tsrather than patching the one symptom:*,_,(,||as leading delimiters atmentions.rs:75andmentions.rs:123;is_word_boundary(mentions.rs:154) to match, so trailing emphasis stops breaking multi-word names;strip_code_regionsbefore@nameextraction, not only before URI extraction.Do not port the TypeScript regex into Rust. That would create a second hand-maintained delimiter list that drifts from the first exactly as the first drifted from the parser — which is this bug. Prefer widening the two existing Rust predicates against one shared "is this a delimiter" definition, and let the tests pin the behaviour. (Direction refined by Prometheus in triage; I originally suggested mirroring the regex and that was the worse call.)
Add Rust unit tests for the emphasis / spoiler / code cases. There are 51 tests in
crates/buzz-sdk/src/mentions.rs(module at line 390+) and 18 more incrates/buzz-cli/src/commands/messages.rs(line 877+) — and not one of the 69 contains an emphasis character next to an@(grep -n '\*\*@\|\*@\|_@' crates/buzz-sdk/src/mentions.rs→ no matches). That gap is why the divergence survived a release.Longer term the two parsers should be one shared implementation, or the divergence recurs.
A cheap independent mitigation, valuable on its own even if the parser is fixed: have
buzz messages sendwarn on stderr when the content contains@tokens but zero mentions resolved. Silent zero-recipient sends are the part that costs real time.Related
Does #2319 / NIP-27 make this moot? No. Checked at
origin/main=acfbb1bb:desktop/—nip27Mentions.ts,rewriteMentionsToNip27.ts,collectMentionPubkeys.ts,hasMention.ts,MessageComposer.tsx,useMentionSendFlow.tsand friends. Zero files undercrates/. Same shape as0f93ef44: the TS side moves, the Rust side does not.extract_nostr_urisatmentions.rs:353— but there is no write path: nothing incrates/rewrites an@nameinto anostr:npub1…ref.So after #2503 lands,
buzz messages sendstill emits bare@nametext and still resolves it throughextract_at_mentions_with_known. The text parser stays load-bearing on the agent path specifically, which is the exact path this bug breaks. Waiting for the NIP-27 work does not save the work.(Verified by Prometheus and independently re-checked during triage.)
Environment
block/buzz@acfbb1bb(main). Originally traced atf8fd055b;crates/buzz-sdk/src/mentions.rsandcrates/buzz-cli/src/commands/messages.rsare byte-identical between the two, so every line number above is current.buzz-clias invoked bybuzz-acpmanaged agentsReported by Prometheus in Buzz
#ditto, 2026-07-23. Triaged by Muse.