block/buzz · error · anyhow::Error

invalid relay key

Error message

invalid relay key: {e}

What it means

reconcile-channels resolves its signing key from --relay-key or BUZZ_RELAY_PRIVATE_KEY and runs it through Keys::parse. The parser accepts only a 32-byte hex secret or a bech32 nsec; anything else — an npub public key, a truncated string, a quoted .env value, or a typo'd nsec — fails here. The key must be the SECRET key because the resulting kind:39000/39001/39002 events are attributed to the relay identity.

Solutions

  1. Use the secret: 64 hex chars or nsec…; verify length: echo -n "$key" | wc -c gives 64 for hex.
  2. Remove quotes/whitespace around the value in .env or the flag argument.
  3. If the relay key was lost, generate a new one and re-run reconcile for ALL channels (no --channel) so every snapshot shares the new identity.
  4. Check the chained {e} — 'bech32' errors mean a typo'd nsec; length errors mean truncation.

Example fix

# before
buzz-admin reconcile-channels --relay-key npub1xyz...  # public key, not secret
# after
buzz-admin reconcile-channels --relay-key nsec1...     # secret key (hex also accepted)
Defensive patterns

Strategy: validation

Validate before calling

# validate before use: 64-hex or nsec, nothing else
RELAY_KEY="${RELAY_KEY:-$BUZZ_RELAY_PRIVATE_KEY}"
printf '%s' "$RELAY_KEY" | grep -qE '^([0-9a-fA-F]{64}|nsec1[a-z0-9]+)$' || { echo 'bad relay key' >&2; exit 3; }

Type guard

fn parse_relay_key(raw: &str) -> Option<nostr::Keys> {
    let s = raw.trim();
    let ok = s.len() == 64 && s.chars().all(|c| c.is_ascii_hexdigit())
        || s.starts_with("nsec1");
    ok.then(|| nostr::Keys::parse(s).ok())?
}

Prevention

When it happens

Trigger: Passing --relay-key <npub-or-wrong-length-string>, or BUZZ_RELAY_PRIVATE_KEY containing quotes/whitespace or a public key.

Common situations: Grabbing the relay's public key from logs because it is easier to find; deploy tooling wrapping env values in quotes; partial copy of a long hex string.

Related errors


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

Appendix: source

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

) -> Result<()> {
    use buzz_core::kind::KIND_NIP29_GROUP_ADMINS;
    use buzz_db::event::EventQuery;

    let db = connect_db().await?;

    // Resolve relay signing key: arg > env > ephemeral. Force-republish must
    // never use an ephemeral key because it replaces an existing authoritative
    // snapshot.
    let configured_relay_key =
        relay_key_arg.or_else(|| std::env::var("BUZZ_RELAY_PRIVATE_KEY").ok());
    if channel_arg.is_some() && configured_relay_key.is_none() {
        return Err(anyhow::anyhow!(
            "--channel requires --relay-key or BUZZ_RELAY_PRIVATE_KEY"
        ));
    }
    let relay_keys = match configured_relay_key {
        Some(key_hex) => {
            Keys::parse(&key_hex).map_err(|e| anyhow::anyhow!("invalid relay key: {e}"))?
        }
        None => {
            let k = Keys::generate();
            eprintln!(
                "Warning: no relay key provided — using ephemeral key {}",
                k.public_key().to_hex()
            );
            eprintln!("Events signed with this key won't be verifiable after this run.");
            eprintln!("Pass --relay-key or set BUZZ_RELAY_PRIVATE_KEY for production use.");
            k
        }
    };

    let tenant = resolve_admin_tenant(&db).await?;
    let target_channel = channel_arg
        .as_deref()
        .map(uuid::Uuid::parse_str)
        .transpose()

View on GitHub (pinned to ef2aa1ae38)