block/buzz · error · anyhow::Error
failed to build member tag: {e}
Error message
failed to build member tag: {e} What it means
buzz-admin's add-member/remove-member path rebuilds the relay-signed kind:13534 (NIP-43 membership list) from the relay_members table, converting every row into a NIP-29 'member' tag with nostr's Tag::parse(["member", pubkey, role]). The nostr parser validates the second element as a Nostr pubkey (32-byte hex or bech32 npub); a malformed pubkey string or an unusable role value makes the parse return Err, which the CLI wraps as this anyhow error. The database write to relay_members has already succeeded at this point — only the roster republication fails.
Source
Thrown at crates/buzz-admin/src/main.rs:366
)
.await?
.map(|e| e.event.created_at.as_secs());
// custom_created_at = max(now, existing + 1s) — defeats same-second domination.
let ts = match newest_ts {
Some(existing) => (existing + 1).max(now),
None => now,
};
let members = db.list_relay_members(tenant.community()).await?;
let mut tags: Vec<Tag> = Vec::with_capacity(members.len() + 1);
// NIP-70 protected-event marker — prevents re-broadcasting by third parties.
tags.push(Tag::parse(["-"]).map_err(|e| anyhow::anyhow!("failed to build '-' tag: {e}"))?);
for member in &members {
tags.push(
Tag::parse(["member", &member.pubkey, &member.role])
.map_err(|e| anyhow::anyhow!("failed to build member tag: {e}"))?,
);
}
let event = EventBuilder::new(Kind::Custom(KIND_NIP43_MEMBERSHIP_LIST as u16), "")
.tags(tags)
.custom_created_at(nostr::Timestamp::from(ts))
.sign_with_keys(relay_keypair)
.map_err(|e| anyhow::anyhow!("failed to sign kind:13534: {e}"))?;
let (stored, was_inserted) = db
.replace_addressable_event(tenant.community(), &event, None)
.await?;
if was_inserted {
// Publish to Redis so live clients receive the updated roster.
// Community-global scope (EventTopic::Global) matches the relay's own
// membership-list publish path; the tenant fixes the community.
if let Err(e) = pubsub
.publish_event(tenant, EventTopic::Global, &stored.event)View on GitHub (pinned to dad5a33865)
Solutions
- Find offending rows: SELECT pubkey, role FROM relay_members WHERE length(pubkey) <> 64 OR pubkey !~ '^[0-9a-f]{64}$';
- Fix each bad row to a 64-char lowercase hex string (UPDATE relay_members SET pubkey = lower(hex_form) WHERE ...), or delete and re-add via buzz-admin add-member.
- Verify role values are plain strings like owner/admin/member with no quotes or escapes.
- Re-run the add-member/remove-member command so the kind:13534 roster is republished.
Example fix
-- before: pubkey stored as escaped raw bytes (from a BYTEA copy) or wrong length
SELECT pubkey FROM relay_members; -- '\x8a0f...' or 40 chars
-- after: canonical lowercase hex
UPDATE relay_members SET pubkey = lower(regexp_replace(pubkey, '^\\x|[^0-9a-fA-F]', '', 'g')) WHERE pubkey !~ '^[0-9a-f]{64}$';
-- if the value is unrecoverable, remove and re-add with the CLI:
-- buzz-admin remove-member --pubkey <bad> ; buzz-admin add-member --pubkey <64-hex-npub-decoded> --role member Defensive patterns
Strategy: validation
Validate before calling
-- run before `buzz-admin add-member/remove-member`
SELECT pubkey, role FROM relay_members
WHERE pubkey !~ '^[0-9a-f]{64}$' OR role IS NULL OR role = '';
-- zero rows means the roster rebuild will pass tag parsing; Prevention
- Always create members through buzz-admin add-member instead of hand-written SQL so pubkeys are validated at write time.
- Add a CI/nightly SQL assertion that relay_members.pubkey matches '^[0-9a-f]{64}$'.
- Keep one storage convention: pubkeys in string columns are always lowercase 64-char hex; raw-byte columns are always 32 bytes.
When it happens
Trigger: Running `buzz-admin add-member` or `buzz-admin remove-member` when list_relay_members returns a row whose pubkey is not 64 lowercase hex chars (wrong length, '0x' prefix, uppercase with mixed input, backslash-escaped BYTEA output copied from psql, or whitespace) or whose role string does not survive tag parsing.
Common situations: Seeding members with hand-written SQL instead of the CLI; storing pubkeys as raw bytes in one code path and hex in another (reconcile_channels hex-encodes m.pubkey, this path uses the string directly); pasting an npub with a newline or space into the DB.
Related errors
- failed to build '-' tag: {e}
- failed to sign kind:13534: {e}
- invalid BUZZ_RELAY_PRIVATE_KEY: {e}
- invalid relay key: {e}
- sign kind:39000: {e}
AI-assisted analysis of block/buzz@dad5a33865 (2026-08-20).
Data as JSON: /api/errors/54ebe9c94bc6c27c.
Report an issue: GitHub.