block/buzz · error · anyhow::Error

sign kind:39000

Error message

sign kind:39000: {e}

What it means

reconcile-channels rebuilds the kind:39000 channel-metadata addressable event (d tag = channel UUID, plus public/hidden/closed/t tags) and signs it with relay_keys via sign_with_keys. All tags were already parsed successfully with Tag::parse(...)?, so a failure at signing comes from the nostr signing step itself: unusable key material or an event that fails to serialize. The {e} chain identifies the nostr Error variant.

Solutions

  1. Read the chained {e} — it names the exact nostr signing error.
  2. Re-verify the signing key: it must be the relay's 32-byte hex/nsec SECRET; re-sign a test event with it in a scratch script to isolate.
  3. Check channel_type and name columns for control characters or invalid UTF-8: SELECT channel_type, name FROM channels WHERE id = '<uuid>';
  4. Re-run reconcile after fixing the key or row data.
Defensive patterns

Strategy: try-catch

Validate before calling

// sign a scratch event with the same key first; if this fails, reconcile will too
let probe = nostr::EventBuilder::new(nostr::Kind::Custom(0), "probe")
    .sign_with_keys(&relay_keys);
assert!(probe.is_ok(), "relay key cannot sign — fix before reconcile");

Try / catch

match reconcile_channel(&channel, &relay_keys).await {
    Ok(_) => {}
    Err(e) if e.to_string().contains("sign kind:39000") => {
        // deterministic signing failure: log channel id + e.source() chain, skip channel, continue others
        skipped.push((channel.id, e.to_string()));
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: The --relay-key/BUZZ_RELAY_PRIVATE_KEY value parsed as a Keys object but cannot produce a signature (corrupt secret material); or an event-serialization error from malformed tag content that survived parsing.

Common situations: Keys that pass a lenient parse but sign incorrectly after a bad key migration; exotic characters in channel_type ending up in the t tag.

Related errors


AI-assisted analysis of block/buzz@ef2aa1ae38 (2026-08-20). Data as JSON: /api/errors/a75e4627e8ae756b. Report an issue: GitHub.

Appendix: source

Thrown at crates/buzz-admin/src/main.rs:731

                    if !desc.is_empty() {
                        tags.push(Tag::parse(["about", desc])?);
                    }
                }
                if channel.visibility == "private" {
                    tags.push(Tag::parse(["private"])?);
                } else {
                    tags.push(Tag::parse(["public"])?);
                }
                if channel.channel_type == "dm" {
                    tags.push(Tag::parse(["hidden"])?);
                }
                tags.push(Tag::parse(["closed"])?);
                tags.push(Tag::parse(["t", &channel.channel_type])?);

                let event = EventBuilder::new(Kind::Custom(39000), "")
                    .tags(tags)
                    .sign_with_keys(&relay_keys)
                    .map_err(|e| anyhow::anyhow!("sign kind:39000: {e}"))?;
                db.replace_addressable_event(tenant.community(), &event, Some(channel.id))
                    .await?;
            }

            // kind:39001 — admins
            {
                let mut tags: Vec<Tag> = vec![Tag::parse(["d", &channel_id_str])?];
                for m in members
                    .iter()
                    .filter(|m| m.role == "owner" || m.role == "admin")
                {
                    let pk = hex::encode(&m.pubkey);
                    tags.push(Tag::parse(["p", &pk, &m.role])?);
                }
                let event = EventBuilder::new(Kind::Custom(KIND_NIP29_GROUP_ADMINS as u16), "")
                    .tags(tags)
                    .sign_with_keys(&relay_keys)
                    .map_err(|e| anyhow::anyhow!("sign kind:39001: {e}"))?;

View on GitHub (pinned to ef2aa1ae38)