block/buzz · error · anyhow::Error
sign kind:39002
Error message
sign kind:39002: {e} What it means
The third reconcile step builds the kind:39002 member-list addressable event (d tag + p tags of (pubkey, '', role) for every member) and signs it with relay_keys. As with 39000/39001, tag parsing already succeeded, so 'sign kind:39002' wraps a nostr signing or event-serialization failure. This event includes ALL members, so a bad pubkey in any role surfaces here first.
Solutions
- Check the chained {e} first.
- Validate every member pubkey: SELECT pubkey, octet_length(pubkey) FROM relay_members; — expect 32 bytes / 64 hex chars.
- Fix or delete offending rows, then re-run reconcile-channels --channel <uuid>.
- If data is clean, suspect the key: re-run with a freshly verified --relay-key.
Defensive patterns
Strategy: validation
Validate before calling
-- kind:39002 includes EVERY member; validate the whole table before reconcile SELECT count(*) FILTER (WHERE octet_length(pubkey) <> 32 OR role IS NULL) AS bad FROM relay_members; -- bad must be 0 before running reconcile-channels
Prevention
- Validate all member rows up front — one corrupt row fails the whole channel's member event.
- Insert members only through validated paths (buzz-admin add-member), never raw SQL.
- Re-run reconcile after any bulk member import as a self-check that all rows serialized.
When it happens
Trigger: Any relay_members row whose pubkey hex-encodes to something nostr cannot serialize into a p tag; or unusable relay signing key material.
Common situations: A single corrupt member row (raw-byte length != 32) poisoning the whole-channel reconcile; key material copied with a stray character.
Related errors
- failed to sign kind:13534
- sign kind:39000
- sign kind:39001
- failed to build member tag
- failed to build '-' tag
AI-assisted analysis of block/buzz@ef2aa1ae38 (2026-08-20).
Data as JSON: /api/errors/d214c3f99903c3c9.
Report an issue: GitHub.
Appendix: source
Thrown at crates/buzz-admin/src/main.rs:765
.tags(tags)
.sign_with_keys(&relay_keys)
.map_err(|e| anyhow::anyhow!("sign kind:39001: {e}"))?;
db.replace_addressable_event(tenant.community(), &event, Some(channel.id))
.await?;
}
}
// kind:39002 — members
{
let mut tags: Vec<Tag> = vec![Tag::parse(["d", &channel_id_str])?];
for m in &members {
let pk = hex::encode(&m.pubkey);
tags.push(Tag::parse(["p", &pk, "", &m.role])?);
}
let event = EventBuilder::new(Kind::Custom(39002), "")
.tags(tags)
.sign_with_keys(&relay_keys)
.map_err(|e| anyhow::anyhow!("sign kind:39002: {e}"))?;
db.replace_addressable_event(tenant.community(), &event, Some(channel.id))
.await?;
}
reconciled += 1;
}
println!(
"Reconciled {reconciled} channels ({skipped} already had events, {} total).",
channels.len()
);
Ok(())
}
#[cfg(test)]
mod storage_snapshot_tests {
use std::sync::atomic::{AtomicUsize, Ordering};
View on GitHub (pinned to ef2aa1ae38)