{"record":{"id":"41d530b34cd9be4c","repo":"block/buzz","slug":"buzz-relay-private-key-is-required-for-add-member","errorCode":null,"errorMessage":"BUZZ_RELAY_PRIVATE_KEY is required for add-member/remove-member.\nThe relay must have a stable signing key to publish kind:13534 events.","messagePattern":"BUZZ_RELAY_PRIVATE_KEY is required for add-member/remove-member\\.\nThe relay must have a stable signing key to publish kind:13534 events\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/buzz-admin/src/main.rs","lineNumber":407,"sourceCode":"    }\n\n    tracing::info!(\n        member_count = members.len(),\n        ts,\n        \"NIP-43 membership list published by buzz-admin\"\n    );\n    Ok(())\n}\n\n/// Connect to DB, Redis pub/sub, and load the relay keypair.\n///\n/// `BUZZ_RELAY_PRIVATE_KEY` is required — the CLI signs kind:13534 events.\nasync fn connect_member_services() -> Result<(Db, Arc<PubSubManager>, Keys)> {\n    let db = connect_db().await?;\n\n    let relay_keypair = {\n        let hex = std::env::var(\"BUZZ_RELAY_PRIVATE_KEY\").map_err(|_| {\n            anyhow::anyhow!(\n                \"BUZZ_RELAY_PRIVATE_KEY is required for add-member/remove-member.\\n\\\n                 The relay must have a stable signing key to publish kind:13534 events.\"\n            )\n        })?;\n        Keys::parse(&hex).map_err(|e| anyhow::anyhow!(\"invalid BUZZ_RELAY_PRIVATE_KEY: {e}\"))?\n    };\n\n    let redis_url =\n        std::env::var(\"REDIS_URL\").unwrap_or_else(|_| \"redis://localhost:6379\".to_string());\n\n    let redis_pool = {\n        let cfg = deadpool_redis::Config::from_url(&redis_url);\n        cfg.create_pool(Some(deadpool_redis::Runtime::Tokio1))\n            .map_err(|e| anyhow::anyhow!(\"Redis pool creation failed: {e}\"))?\n    };\n\n    let pubsub = Arc::new(\n        PubSubManager::new(&redis_url, redis_pool)","sourceCodeStart":389,"sourceCodeEnd":425,"githubUrl":"https://github.com/block/buzz/blob/f956e6fe06a76e50cbd8fba1a162482e752e7f1a/crates/buzz-admin/src/main.rs#L389-L425","documentation":"Thrown by buzz-admin's connect_member_services() when the add-member/remove-member subcommands run without BUZZ_RELAY_PRIVATE_KEY set in the environment. These commands publish a signed NIP-43 kind:13534 membership list on behalf of the relay, which requires the relay's stable Nostr signing keypair; there is deliberately no ephemeral fallback because clients verify the roster against the relay's known pubkey. The error fires before any DB or Redis work beyond connect_db().","triggerScenarios":"Running `buzz-admin add-member ...` or `buzz-admin remove-member ...` in a shell/process where BUZZ_RELAY_PRIVATE_KEY is unset (e.g. only RELAY_URL and DATABASE_URL exported), or running the CLI from a service unit or cron that does not source the relay's .env file.","commonSituations":"Operator copies the relay's .env.example but only fills in database/relay URLs; running buzz-admin on a different host than the relay without copying the key; CI scripts that intentionally omit secrets. Note the env-deletion subcommands (buzz-deletion) and reconcile-channels have different key rules, so a setup that works for those fails here.","solutions":["Export the relay's signing key in the shell before running: BUZZ_RELAY_PRIVATE_KEY=<64-char hex> (copy the same value the relay process uses, so the published kind:13534 events are verifiable by clients).","Put it in the environment file your workflow sources (e.g. .env next to the relay config) and re-run the command from that shell.","If running under systemd/cron/docker, add BUZZ_RELAY_PRIVATE_KEY to that environment (Environment=, crontab env line, or compose env_file) — a login shell export is invisible to those contexts.","Never generate a fresh key for this command: events signed by any key other than the relay's own key will be rejected/ignored by clients as untrusted."],"exampleFix":"# before\nbuzz-admin add-member --pubkey <hex> --role member\n# error: BUZZ_RELAY_PRIVATE_KEY is required for add-member/remove-member.\n\n# after\nexport BUZZ_RELAY_PRIVATE_KEY=$(grep '^BUZZ_RELAY_PRIVATE_KEY=' /path/to/relay/.env | cut -d= -f2)\nbuzz-admin add-member --pubkey <hex> --role member","handlingStrategy":"validation","validationCode":"#!/usr/bin/env bash\n# preflight before buzz-admin add-member/remove-member\nif [ -z \"${BUZZ_RELAY_PRIVATE_KEY:-}\" ]; then\n  echo \"BUZZ_RELAY_PRIVATE_KEY is not set — export the relay's secret key\" >&2\n  exit 1\nfi","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Source the relay's .env in every shell/script that runs buzz-admin member commands.","Store the relay secret in your secret manager under a dedicated key so wrappers can inject it unattended.","Remember the rule split: reconcile-channels only needs a key with --channel; add-member/remove-member always do."],"tags":["buzz-admin","env-var","nostr","signing-key","configuration"],"backgroundTag":"missing-env-var","analyzedSha":"f956e6fe06a76e50cbd8fba1a162482e752e7f1a","analyzedAt":"2026-08-16T22:11:40.750Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}