block / block/buzz

[Bug] Bold-wrapped @mentions (**@Name**) send zero p tags from buzz-cli — no notification, exit 0, no warning

Open
#2,526 1 comment 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

**Describe the bug**

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**

```bash
buzz messages send --channel --content '**@Alice** please take a look'
# exit 0, message appears in the channel, zero p tags on the event

buzz messages send --channel --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 `; `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:

```rust
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`:

```ts
const pattern = new RegExp(
`(^|\\s|\\(|[*_]{1,3}|\\|\\|)(@${escaped})(?=\\|\\||[\\s,;.!?:)\\]}*_]|$)`,
"i",
);
```

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**

1. **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.

2. **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**

- #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.
- #1743 / #1741 / #1742 — @mentions silently fail when the target agent is offline. Same failure class: the mention does not deliver and the sender gets no feedback.
- #2319 — mentions should use NIP-27. **Checked: it does not supersede this. See below — do not re-ask.**

**Does #2319 / NIP-27 make this moot? No.** Checked at `origin/main` = `acfbb1bb`:

- #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.

Contributor guide

Open the contributing guide

Research direction

Start with crates/buzz-sdk/src/mentions.rs, especially extract_at_names, extract_at_mentions_with_known, and is_word_boundary, then inspect resolve_content_mentions in crates/buzz-cli/src/commands/messages.rs. Compare the Rust behavior with desktop/src/features/messages/lib/hasMention.ts and its code masking. Add coverage in the existing Rust mention tests for emphasis, spoilers, multi-word names, and code regions, with matching mention resolution and no code-block false positives as the done criteria.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust, typescript
Domain
backend, cli
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.