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

  1. Check the chained {e} first.
  2. Validate every member pubkey: SELECT pubkey, octet_length(pubkey) FROM relay_members; — expect 32 bytes / 64 hex chars.
  3. Fix or delete offending rows, then re-run reconcile-channels --channel <uuid>.
  4. 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

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


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)