block/buzz · error · anyhow::Error
invalid BUZZ_RELAY_PRIVATE_KEY
Error message
invalid BUZZ_RELAY_PRIVATE_KEY: {e} What it means
Keys::parse accepts only a 32-byte hex secret key or a bech32 nsec… string. Any other shape — truncated or padded hex, '0x' prefix, an npub (public key) pasted by mistake, surrounding quotes/whitespace from a .env file, or a passphrase — returns Err, surfaced as 'invalid BUZZ_RELAY_PRIVATE_KEY'. The check runs after the presence check, so the variable is set but its value is not a usable secret.
Solutions
- Hex form must be exactly 64 chars: echo -n "$BUZZ_RELAY_PRIVATE_KEY" | wc -c (expect 64).
- If using nsec form, re-copy it fully — bech32 checksums fail loudly on any typo.
- Make sure it is the SECRET key (hex or nsec), not the npub/public key.
- Strip quotes/whitespace: export BUZZ_RELAY_PRIVATE_KEY=$(echo "$BUZZ_RELAY_PRIVATE_KEY" | tr -d '"' | tr -d "'").
Example fix
# before BUZZ_RELAY_PRIVATE_KEY="8a3f..." # quotes stored as part of the value by a deploy tool # after BUZZ_RELAY_PRIVATE_KEY=8a3f... # bare 64-hex value, no quotes
Defensive patterns
Strategy: validation
Validate before calling
# accept only 64-hex or nsec… before running the command
key="$BUZZ_RELAY_PRIVATE_KEY"
case "$key" in
nsec1*) ;;
*) [ "${#key}" -eq 64 ] && echo "$key" | grep -qE '^[0-9a-fA-F]{64}$' || { echo 'bad key format' >&2; exit 3; } ;;
esac Type guard
fn is_usable_secret(key: &str) -> bool {
let k = key.trim();
(k.len() == 64 && k.chars().all(|c| c.is_ascii_hexdigit()))
|| k.starts_with("nsec1")
} Prevention
- Never wrap key values in quotes in .env files.
- Store the secret as nsec if humans copy it — bech32 checksums catch truncation.
- Label keys unambiguously (RELAY_SECRET vs RELAY_PUBLIC) in secret stores so the npub is never grabbed by mistake.
When it happens
Trigger: BUZZ_RELAY_PRIVATE_KEY contains an npub/public key, a 63- or 65-char string, a quoted value from .env, or an nsec with a typo (bech32 checksum failure).
Common situations: Copy-paste truncation; using the public key because it was the handier string from the relay logs; .env values wrapped in quotes by a deployment tool.
Related errors
- invalid relay key
- BUZZ_RELAY_PRIVATE_KEY is required for…
- BUZZ_RELAY_PRIVATE_KEY must be set when…
- channel not found
- failed to build member tag
AI-assisted analysis of block/buzz@ef2aa1ae38 (2026-08-20).
Data as JSON: /api/errors/18a6ef2bf2c36ea8.
Report an issue: GitHub.
Appendix: source
Thrown at crates/buzz-admin/src/main.rs:557
"NIP-43 membership list published by buzz-admin"
);
Ok(())
}
/// Connect to DB, Redis pub/sub, and load the relay keypair.
///
/// `BUZZ_RELAY_PRIVATE_KEY` is required — the CLI signs kind:13534 events.
async fn connect_member_services() -> Result<(Db, Arc<PubSubManager>, Keys)> {
let db = connect_db().await?;
let relay_keypair = {
let hex = std::env::var("BUZZ_RELAY_PRIVATE_KEY").map_err(|_| {
anyhow::anyhow!(
"BUZZ_RELAY_PRIVATE_KEY is required for add-member/remove-member.\n\
The relay must have a stable signing key to publish kind:13534 events."
)
})?;
Keys::parse(&hex).map_err(|e| anyhow::anyhow!("invalid BUZZ_RELAY_PRIVATE_KEY: {e}"))?
};
let redis_url =
std::env::var("REDIS_URL").unwrap_or_else(|_| "redis://localhost:6379".to_string());
let redis_pool = {
let cfg = deadpool_redis::Config::from_url(&redis_url);
cfg.create_pool(Some(deadpool_redis::Runtime::Tokio1))
.map_err(|e| anyhow::anyhow!("Redis pool creation failed: {e}"))?
};
let pubsub = Arc::new(
PubSubManager::new(&redis_url, redis_pool)
.await
.map_err(|e| anyhow::anyhow!("PubSub init failed: {e}"))?,
);
Ok((db, pubsub, relay_keypair))View on GitHub (pinned to ef2aa1ae38)