{"record":{"id":"c0136ceef17c61e3","repo":"block/buzz","slug":"invalid-relay-key-e","errorCode":null,"errorMessage":"invalid relay key: {e}","messagePattern":"invalid relay key: (.+?)","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/buzz-admin/src/main.rs","lineNumber":643,"sourceCode":") -> Result<()> {\n    use buzz_core::kind::KIND_NIP29_GROUP_ADMINS;\n    use buzz_db::event::EventQuery;\n\n    let db = connect_db().await?;\n\n    // Resolve relay signing key: arg > env > ephemeral. Force-republish must\n    // never use an ephemeral key because it replaces an existing authoritative\n    // snapshot.\n    let configured_relay_key =\n        relay_key_arg.or_else(|| std::env::var(\"BUZZ_RELAY_PRIVATE_KEY\").ok());\n    if channel_arg.is_some() && configured_relay_key.is_none() {\n        return Err(anyhow::anyhow!(\n            \"--channel requires --relay-key or BUZZ_RELAY_PRIVATE_KEY\"\n        ));\n    }\n    let relay_keys = match configured_relay_key {\n        Some(key_hex) => {\n            Keys::parse(&key_hex).map_err(|e| anyhow::anyhow!(\"invalid relay key: {e}\"))?\n        }\n        None => {\n            let k = Keys::generate();\n            eprintln!(\n                \"Warning: no relay key provided — using ephemeral key {}\",\n                k.public_key().to_hex()\n            );\n            eprintln!(\"Events signed with this key won't be verifiable after this run.\");\n            eprintln!(\"Pass --relay-key or set BUZZ_RELAY_PRIVATE_KEY for production use.\");\n            k\n        }\n    };\n\n    let tenant = resolve_admin_tenant(&db).await?;\n    let target_channel = channel_arg\n        .as_deref()\n        .map(uuid::Uuid::parse_str)\n        .transpose()","sourceCodeStart":625,"sourceCodeEnd":661,"githubUrl":"https://github.com/block/buzz/blob/ef2aa1ae38fadcc0bc22b8bf6ed96b35933146be/crates/buzz-admin/src/main.rs#L625-L661","documentation":"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.","triggerScenarios":"Passing --relay-key <npub-or-wrong-length-string>, or BUZZ_RELAY_PRIVATE_KEY containing quotes/whitespace or a public key.","commonSituations":"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.","solutions":["Use the secret: 64 hex chars or nsec…; verify length: echo -n \"$key\" | wc -c gives 64 for hex.","Remove quotes/whitespace around the value in .env or the flag argument.","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.","Check the chained {e} — 'bech32' errors mean a typo'd nsec; length errors mean truncation."],"exampleFix":"# before\nbuzz-admin reconcile-channels --relay-key npub1xyz...  # public key, not secret\n# after\nbuzz-admin reconcile-channels --relay-key nsec1...     # secret key (hex also accepted)","handlingStrategy":"validation","validationCode":"# validate before use: 64-hex or nsec, nothing else\nRELAY_KEY=\"${RELAY_KEY:-$BUZZ_RELAY_PRIVATE_KEY}\"\nprintf '%s' \"$RELAY_KEY\" | grep -qE '^([0-9a-fA-F]{64}|nsec1[a-z0-9]+)$' || { echo 'bad relay key' >&2; exit 3; }","typeGuard":"fn parse_relay_key(raw: &str) -> Option<nostr::Keys> {\n    let s = raw.trim();\n    let ok = s.len() == 64 && s.chars().all(|c| c.is_ascii_hexdigit())\n        || s.starts_with(\"nsec1\");\n    ok.then(|| nostr::Keys::parse(s).ok())?\n}","tryCatchPattern":null,"preventionTips":["Keep exactly one canonical copy of the relay secret in your secret store; never retype it.","Automate key handoff: read --relay-key from the secret manager in the run script, not from a pasted shell history.","After any key rotation, re-run reconcile without --channel so all channel events share one identity."],"tags":["buzz-admin","nostr","private-key","reconcile-channels","validation"],"backgroundTag":"invalid-private-key","analyzedSha":"ef2aa1ae38fadcc0bc22b8bf6ed96b35933146be","analyzedAt":"2026-08-20T04:38:24.874Z","contentChangedAt":"2026-08-20T04:38:24.874Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}