{"record":{"id":"89aedd31ed184786","repo":"block/buzz","slug":"kind-30617-missing-d-tag","errorCode":null,"errorMessage":"kind:30617 missing d tag","messagePattern":"kind:30617 missing d tag","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/buzz-relay/src/handlers/side_effects.rs","lineNumber":2568,"sourceCode":"\n#[cfg(test)]\n#[derive(Default)]\npub(crate) struct GitRepoAnnouncementGate {\n    pub(crate) reached: tokio::sync::Notify,\n    pub(crate) resume: tokio::sync::Notify,\n}\n\npub(crate) async fn handle_git_repo_announcement_inner(\n    tenant: &TenantContext,\n    event: &Event,\n    state: &Arc<AppState>,\n    hooks: &GitRepoAnnouncementHooks,\n) -> anyhow::Result<()> {\n    #[cfg(not(test))]\n    let _ = hooks;\n    // Extract repo identifier from d tag (required for NIP-33 parameterized replaceable events).\n    let repo_id =\n        extract_tag_value(event, \"d\").ok_or_else(|| anyhow::anyhow!(\"kind:30617 missing d tag\"))?;\n\n    if !validate_repo_id(&repo_id) {\n        return Err(anyhow::anyhow!(\n            \"invalid repo identifier: must be [a-zA-Z0-9._-]{{1,64}}, no leading dots, no '..'\"\n        ));\n    }\n\n    let owner_hex = hex::encode(event.pubkey.to_bytes());\n\n    // The relay holds no persistent per-repo disk state: runtime reads and\n    // writes hydrate an ephemeral bare repo from object storage per request\n    // (see `api::git::hydrate`). Announce only (1) reserves the repo name and\n    // (2) seeds the empty-manifest pointer that makes the repo clone-able.\n    //\n    // The `git_repo_names` table (Postgres) is the relay's name registry,\n    // keyed `(community_id, repo_id)`. It serves three jobs at once inside the\n    // server-resolved community boundary:\n    //   - uniqueness: `INSERT … ON CONFLICT DO NOTHING` is atomic, so","sourceCodeStart":2550,"sourceCodeEnd":2586,"githubUrl":"https://github.com/block/buzz/blob/dad5a33865fc81a2e55b3b60746632f615ec1e3a/crates/buzz-relay/src/handlers/side_effects.rs#L2550-L2586","documentation":"handle_git_repo_announcement_inner processes kind:30617 git repo announcement events. NIP-33 parameterized replaceable events require a `d` tag as the replaceable identifier; extract_tag_value returns None when the event has no `d` tag, and this anyhow error is raised. The event cannot be processed without a repo identifier.","triggerScenarios":"Publishing (or rebroadcasting) a kind:30617 event whose tags contain no `d` tag — e.g. a hand-crafted event, a client bug omitting the tag, or a tag spelled differently (case-sensitive) such as `D`.","commonSituations":"Custom git-sync tooling constructing announcements manually without NIP-33 `d`; older client versions predating the d-tag requirement; copy-pasted event templates where the d tag was stripped by JSON tooling.","solutions":["Add a `d` tag containing the repo identifier to the kind:30617 event and re-sign before publishing.","Ensure the tag value matches [a-zA-Z0-9._-]{1,64} with no leading dots and no '..' so it passes validate_repo_id next.","Upgrade the publishing client to a version that emits NIP-33-compliant `d` tags for announcements."],"exampleFix":"// before\ntags: [[\"t\", \"myrepo\"]],\n// after\ntags: [[\"d\", \"myrepo\"], [\"t\", \"myrepo\"]],","handlingStrategy":"validation","validationCode":"fn has_d_tag(tags: &[[String;2]]) -> bool {\n    tags.iter().any(|t| t[0] == \"d\" && !t[1].is_empty())\n}","typeGuard":"fn extract_valid_d<'a>(event: &'a Event) -> Option<&'a str> {\n    event.tags.iter().find_map(|t| (t.kind().as_str() == \"d\")\n        .then(|| t.content()).flatten().filter(|c| !c.is_empty()))\n}","tryCatchPattern":"match handle_git_repo_announcement(&event).await {\n    Err(e) if e.to_string().contains(\"missing d tag\") => {\n        return Err(anyhow!(\"client bug: kind:30617 requires a NIP-33 d tag\"));\n    }\n    r => r,\n}","preventionTips":["Always emit a `d` tag on kind:30617 per NIP-33 before signing.","Use a typed event builder rather than raw tag arrays.","Unit-test announcement builders for required tags."],"tags":["nostr","nip-33","git","validation"],"backgroundTag":"missing-required-tag","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"}