{"record":{"id":"68b8bc18dd807cd3","repo":"block/buzz","slug":"target-event-not-found","errorCode":null,"errorMessage":"target event not found","messagePattern":"target event not found","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/buzz-relay/src/handlers/side_effects.rs","lineNumber":271,"sourceCode":"        let target_pubkey_bytes =\n            hex::decode(parts[1]).map_err(|_| anyhow::anyhow!(\"invalid pubkey in a-tag\"))?;\n        if target_pubkey_bytes != actor_bytes\n            && !state\n                .db\n                .is_agent_owner(tenant.community(), &target_pubkey_bytes, &actor_bytes)\n                .await?\n        {\n            return Err(anyhow::anyhow!(\"must be event author\"));\n        }\n        return Ok(());\n    }\n\n    for target_id in target_ids {\n        let target_event = state\n            .db\n            .get_event_by_id_including_deleted_for_event_write(tenant.community(), &target_id)\n            .await?\n            .ok_or_else(|| anyhow::anyhow!(\"target event not found\"))?;\n\n        let target_author =\n            effective_message_author(&target_event.event, &state.relay_keypair.public_key());\n        if target_author != actor_bytes\n            && !state\n                .db\n                .is_agent_owner(tenant.community(), &target_author, &actor_bytes)\n                .await?\n        {\n            return Err(anyhow::anyhow!(\"must be event author\"));\n        }\n    }\n\n    Ok(())\n}\n\n/// Returns `true` if `actor_bytes` is the NIP-OA owner of **any** active owner-role\n/// member in `members`. Used by kind:9002 and kind:9008 to authorize the owning","sourceCodeStart":253,"sourceCodeEnd":289,"githubUrl":"https://github.com/block/buzz/blob/dad5a33865fc81a2e55b3b60746632f615ec1e3a/crates/buzz-relay/src/handlers/side_effects.rs#L253-L289","documentation":"In the relay's kind:9005 (NIP-29 delete event) side-effect handler, the target message is taken from the event's e tag and looked up with db.get_event_by_id scoped to the community. The path fails closed: if no stored event with that id exists, the delete is rejected before anything is stored — you cannot delete (or tombstone) a message the relay never accepted. This also guards against cross-community ids: an id that exists on another relay is still 'not found' here.","triggerScenarios":"A client publishes kind:9005 whose e tag references an id that was never stored on this community, was already hard-deleted/purged (e.g., by buzz-deletion), was truncated or mangled during copy, or belongs to a different relay/community.","commonSituations":"Retrying an old delete after the target was purged by retention tooling; a client replaying a queue of deletes against a fresh/restored database; pointing a staging client at production (or vice versa) so ids never match.","solutions":["Verify the target exists on this relay before sending: query it (buzz messages thread --channel <ch> --event <id>, or POST /query with an ids filter).","If the row was purged by deletion tooling, no delete is needed — the message is already gone; drop the request.","Check the e tag is the full 64-char hex id with no whitespace or truncation.","Confirm client and tooling talk to the same community (same RELAY_URL host) — an id from another community will never resolve."],"exampleFix":"// before: fire-and-forget delete against an id that may be gone\nawait relay.publish(deleteEvent({ target: maybeStaleId, channel }));\n// after: verify existence first, treat 9005 'target event not found' as success for purged ids\nconst existing = await relay.query({ ids: [targetId], kinds: [MESSAGE_KINDS] });\nif (existing.length === 0) { /* already gone; nothing to delete */ }","handlingStrategy":"validation","validationCode":"// client-side: confirm the target exists in this community before publishing 9005\nconst rows = await relay.query({ ids: [targetId], limit: 1 });\nif (rows.length === 0) {\n  // treat as already-deleted: publish nothing, report success to the user\n  return { ok: true, noop: true };\n}","typeGuard":null,"tryCatchPattern":"try {\n  await relay.publish(deleteEvent);\n} catch (e) {\n  if (String(e).includes('target event not found')) {\n    // idempotent delete: the target is gone from this community — swallow\n    return { ok: true, noop: true };\n  }\n  throw e;\n}","preventionTips":["Make delete flows idempotent: 'not found' after a purge is success, not an error.","Build delete events from ids obtained by querying the same relay, never from another environment's data.","After running buzz-deletion retention jobs, drop queued deletes for purged ids instead of replaying them."],"tags":["buzz-relay","nostr","delete-event","kind-9005","fail-closed"],"backgroundTag":"referenced-event-not-found","analyzedSha":"dad5a33865fc81a2e55b3b60746632f615ec1e3a","analyzedAt":"2026-09-05T18:13:50.666Z","contentChangedAt":"2026-09-05T18:13:50.666Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}