{"record":{"id":"3128fe74169a5b83","repo":"block/buzz","slug":"failed-to-sign-member-snapshot-error","errorCode":null,"errorMessage":"failed to sign member snapshot: {error}","messagePattern":"failed to sign member snapshot: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/buzz-relay/src/handlers/side_effects.rs","lineNumber":1017,"sourceCode":"    let ts = member_snapshot\n        .latest_member_event_timestamp(tenant.community(), channel_id, &relay_pubkey)\n        .await?\n        .map(|timestamp| timestamp + 1)\n        .unwrap_or(now)\n        .max(now);\n    // A relay-signed roster of a channel the relay is itself a member of (the\n    // relay's moderation-DM key participates in the {relay, recipient} DM used\n    // for moderation notices) MUST retain the relay's own `p` tag. nostr's\n    // default `build_with_ctx` strips any `p` tag matching the signer, which\n    // would drop the relay from the snapshot and fail migration 0032's roster\n    // fence against the canonical two-member DM. `allow_self_tagging` keeps the\n    // snapshot faithful to `channel_members`.\n    let event = EventBuilder::new(Kind::Custom(KIND_NIP29_GROUP_MEMBERS as u16), \"\")\n        .tags(tags)\n        .allow_self_tagging()\n        .custom_created_at(nostr::Timestamp::from(ts))\n        .sign_with_keys(&state.relay_keypair)\n        .map_err(|error| anyhow::anyhow!(\"failed to sign member snapshot: {error}\"))?;\n    let (stored, inserted) = member_snapshot\n        .replace_member_event(tenant.community(), channel_id, &event)\n        .await?;\n    Ok(inserted.then_some(stored))\n}\n\nasync fn dispatch_group_members_event(\n    tenant: &TenantContext,\n    state: &Arc<AppState>,\n    stored: Option<buzz_core::StoredEvent>,\n    relay_pubkey_hex: &str,\n) {\n    if let Some(stored) = stored {\n        dispatch_persistent_event(\n            tenant,\n            state,\n            &stored,\n            KIND_NIP29_GROUP_MEMBERS,","sourceCodeStart":999,"sourceCodeEnd":1035,"githubUrl":"https://github.com/block/buzz/blob/dad5a33865fc81a2e55b3b60746632f615ec1e3a/crates/buzz-relay/src/handlers/side_effects.rs#L999-L1035","documentation":"store_group_members_event builds the NIP-29 kind:39002 member snapshot event and signs it with the relay's own identity keypair via sign_with_keys. This error wraps any failure from the nostr signing operation, meaning the relay could not produce a valid signature over the snapshot event. It is a server-internal error — the snapshot was constructed but never signed or published.","triggerScenarios":"Calling store_group_members_event (directly or via emit_group_discovery_events / reconcile_large_channel_member_snapshots) when the relay_keypair held in state is invalid, corrupted, or fails during signing. Happens when the keypair was constructed from a malformed BUZZ_RELAY_PRIVATE_KEY or the nostr Keys object became unusable.","commonSituations":"Relay started with a misconfigured BUZZ_RELAY_PRIVATE_KEY (wrong length, non-hex, zero key); a relay keypair that parses but is rejected by the nostr library during secp256k1 signing; environment drift after manual edits to .env; running reconcile against a relay identity that was regenerated.","solutions":["Verify BUZZ_RELAY_PRIVATE_KEY is a valid 64-char hex secp256k1 private key; relay startup already validates via nostr::Keys::parse — re-run with a fresh `just bootstrap` key if unsure.","Restart the relay so state.relay_keypair is rebuilt from configuration rather than a stale value.","Check the wrapped {error} detail in logs for the underlying secp256k1/nostr failure and fix that root cause.","Run `just test-unit` for the relay crate to confirm keypair/signing setup after config changes."],"exampleFix":"// before: keypair loaded from ad-hoc string\nlet keys = Keys::parse(env::var(\"RELAY_KEY\").unwrap().as_str())?;\n// after: validated at startup via relay_keypair_from_config\nlet keys = relay_keypair_from_config(config.relay_private_key.as_deref())?;","handlingStrategy":"try-catch","validationCode":"let keys = nostr::Keys::parse(&hex_key)?;\nif keys.public_key().to_bytes().len() != 32 { return Err(anyhow!(\"bad relay key\")); }","typeGuard":null,"tryCatchPattern":"match store_group_members_event(...) {\n    Err(e) if e.to_string().contains(\"failed to sign member snapshot\") => {\n        tracing::error!(\"relay keypair unusable: {e:#}\");\n        // reload keypair from config and retry once\n    }\n    other => other?,\n}","preventionTips":["Validate BUZZ_RELAY_PRIVATE_KEY with nostr::Keys::parse at startup before serving traffic.","Never hand-roll key loading; use relay_keypair_from_config as the single source.","Log the wrapped signing error chain ({error:#}) for root cause."],"tags":["rust","nostr","signing","relay"],"backgroundTag":"event-signing-failed","analyzedSha":"dad5a33865fc81a2e55b3b60746632f615ec1e3a","analyzedAt":"2026-08-30T13:49:18.474Z","contentChangedAt":"2026-08-30T13:49:18.474Z","schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}