{"record":{"id":"ed5de0059f881a33","repo":"block/buzz","slug":"failed-to-sign-system-message-e","errorCode":null,"errorMessage":"failed to sign system message: {e}","messagePattern":"failed to sign system message: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/buzz-relay/src/handlers/side_effects.rs","lineNumber":709,"sourceCode":"/// `insert_event` then provides DB-enforced delivery idempotency.\n///\n/// Returns `Err` if the event could not be durably inserted; fanout remains\n/// best-effort.\npub async fn emit_system_message(\n    tenant: &TenantContext,\n    state: &Arc<AppState>,\n    channel_id: Uuid,\n    content: serde_json::Value,\n    idempotency_ts: chrono::DateTime<chrono::Utc>,\n) -> anyhow::Result<()> {\n    let channel_tag = Tag::parse([\"h\", &channel_id.to_string()])?;\n\n    let ts = nostr::Timestamp::from(idempotency_ts.timestamp() as u64);\n    let event = EventBuilder::new(Kind::Custom(40099), content.to_string())\n        .tags([channel_tag])\n        .custom_created_at(ts)\n        .sign_with_keys(&state.relay_keypair)\n        .map_err(|e| anyhow::anyhow!(\"failed to sign system message: {e}\"))?;\n\n    // Durable insert is the completion boundary — propagate failure.\n    state\n        .db\n        .insert_event(tenant.community(), &event, Some(channel_id))\n        .await\n        .map_err(|e| anyhow::anyhow!(\"system message insert failed: {e}\"))?;\n\n    // Fan out to subscribers: best-effort, clients can retrieve the persisted event.\n    if let Err(e) = state\n        .pubsub\n        .publish_event(tenant, EventTopic::Channel(channel_id), &event)\n        .await\n    {\n        warn!(\"System message fan-out failed: {e}\");\n    }\n\n    Ok(())","sourceCodeStart":691,"sourceCodeEnd":727,"githubUrl":"https://github.com/block/buzz/blob/dad5a33865fc81a2e55b3b60746632f615ec1e3a/crates/buzz-relay/src/handlers/side_effects.rs#L691-L727","documentation":"`emit_system_message` builds a relay-signed Nostr kind:40099 system event and signs it with the relay keypair via `sign_with_keys`. This error means the signing step failed — most commonly a malformed tag/content or a secp256k1 signing error — before any DB write occurs. It propagates to callers like handle_create_group / handle_put_user so the triggering operation can fail rather than silently skip the system message.","triggerScenarios":"Any caller (handle_dm_open, handle_put_user, handle_remove_user, handle_edit_metadata, handle_delete_event_side_effect, handle_create_group) invoking emit_system_message when `EventBuilder::...sign_with_keys(&state.relay_keypair)` errors — e.g. `state.relay_keypair` is None/uninitialized because relay signing keys were not configured, or the key bytes are invalid.","commonSituations":"Relay started without NOSTR secret key configured so keypair construction produced a placeholder/invalid key; bad hex or short key in env config; nostr crate version change altering keypair parsing; corrupted key material in state.","solutions":["Check the relay's secret-key configuration (env/config file) and confirm the relay keypair initializes correctly at startup.","Inspect the inner `{e}`: a secp256k1 error means invalid key bytes; fix the key source (must be 32-byte valid secret key).","Fail fast at relay startup with an explicit error if the signing keypair cannot be constructed, instead of deferring to message emit time.","Verify the nostr crate version's `sign_with_keys` API and key type match what AppState stores.","If keys come from a secret store, confirm the secret was loaded (not empty) before handling events."],"exampleFix":"// before: keypair failure discovered only at emit time\n.sign_with_keys(&state.relay_keypair)\n.map_err(|e| anyhow::anyhow!(\"failed to sign system message: {e}\"))?;\n// after: validate at startup\nlet relay_keypair = KeyPair::from_secret(&secret)\n    .context(\"relay signing key invalid — check NOSTR_SECRET_KEY\")?; // fail at boot\n// then in emit:\n.sign_with_keys(&state.relay_keypair)?;","handlingStrategy":"validation","validationCode":"// at relay startup, before serving:\nlet relay_keypair = KeyPair::from_secret(&secret_bytes)\n    .context(\"relay signing key invalid/unconfigured — check NOSTR_SECRET_KEY\")?;\nassert!(!secret_hex.is_empty(), \"relay signing key must be set\");","typeGuard":"fn has_valid_relay_keypair(state: &AppState) -> bool {\n    // secp256k1 secret keys are 32 bytes; verify the keypair can sign\n    KeyPair::from_secret(state.secret_key_bytes()).is_ok()\n}","tryCatchPattern":"match emit_system_message(&tenant, &state, channel_id, content, ts).await {\n    Ok(()) => (),\n    Err(e) if format!(\"{e:#}\").contains(\"failed to sign system message\") => {\n        error!(\"relay signing key misconfigured: {e:#}\"); // config issue, not transient\n        return Err(e);\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Fail fast at boot when the signing keypair cannot be constructed — never defer to first emit.","Keep the relay secret key in a validated, versioned config; test key loading in CI.","Pin the nostr crate version and re-test key parsing on upgrades.","Alert on 'failed to sign system message' — it almost always means configuration, not runtime, trouble."],"tags":["nostr","cryptography","signing","configuration","rust"],"backgroundTag":"invalid-signing-key","analyzedSha":"dad5a33865fc81a2e55b3b60746632f615ec1e3a","analyzedAt":"2026-08-30T13:49:18.474Z","contentChangedAt":"2026-08-30T13:49:18.474Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}