{"record":{"id":"7f303c6927c92498","repo":"block/buzz","slug":"kind-9002-must-include-at-least-one-metadata-tag","errorCode":null,"errorMessage":"kind:9002 must include at least one metadata tag (name, about, archived, topic, purpose, visibility, ttl)","messagePattern":"kind:9002 must include at least one metadata tag \\(name, about, archived, topic, purpose, visibility, ttl\\)","errorType":"validation","errorClass":"IngestError::Rejected","httpStatus":400,"severity":"error","filePath":"crates/buzz-relay/src/handlers/side_effects.rs","lineNumber":508,"sourceCode":"            }\n        }\n        9002 => {\n            // EDIT_METADATA: require at least one recognized metadata tag.\n            const RECOGNIZED_TAGS: &[&str] = &[\n                \"name\",\n                \"about\",\n                \"archived\",\n                \"topic\",\n                \"purpose\",\n                \"visibility\",\n                \"ttl\",\n            ];\n            let has_recognized = event\n                .tags\n                .iter()\n                .any(|t| RECOGNIZED_TAGS.contains(&t.kind().to_string().as_str()));\n            if !has_recognized {\n                return Err(anyhow::anyhow!(\n                    \"kind:9002 must include at least one metadata tag (name, about, archived, topic, purpose, visibility, ttl)\"\n                ));\n            }\n\n            // Validate archived values before storage.\n            for t in event.tags.iter() {\n                if t.kind().to_string() == \"archived\" {\n                    match t.content() {\n                        Some(\"true\") | Some(\"false\") => {}\n                        Some(v) => {\n                            return Err(anyhow::anyhow!(\n                                \"invalid archived value: {v} (must be \\\"true\\\" or \\\"false\\\")\"\n                            ));\n                        }\n                        None => {\n                            return Err(anyhow::anyhow!(\"archived tag must have a value\"));\n                        }\n                    }","sourceCodeStart":490,"sourceCodeEnd":526,"githubUrl":"https://github.com/block/buzz/blob/f956e6fe06a76e50cbd8fba1a162482e752e7f1a/crates/buzz-relay/src/handlers/side_effects.rs#L490-L526","documentation":"Thrown when a kind:9002 (EDIT_METADATA) event contains none of the recognized metadata tags: name, about, archived, topic, purpose, visibility, ttl. The relay requires at least one recognized tag before evaluating permissions or storing the edit. Unrecognized tags (including typos like \"topics\" or \"names\") do not count.","triggerScenarios":"Publishing kind:9002 with only unknown tags, e.g. [[\"h\", ch], [\"emoji\", \"🔥\"], [\"picture\", url]], or with a misspelled tag kind such as [\"desc\", ...] instead of [\"about\", ...].","commonSituations":"Porting NIP-29 edit payloads from other relay implementations that accept extra tags; SDK versions that emit different tag names; hand-built events where a trailing 's' or camelCase tag slips in.","solutions":["Add at least one recognized tag: [\"name\", ...], [\"about\", ...], [\"archived\", ...], [\"topic\", ...], [\"purpose\", ...], [\"visibility\", ...], or [\"ttl\", ...].","Check for tag typos — the matcher compares the tag kind string exactly (lowercase, singular).","If you only meant to change topic/purpose, those count as recognized and additionally only require membership.","Validate the tag set client-side before publishing (see defense)."],"exampleFix":"// before — no recognized metadata tag\n{ kind: 9002, tags: [[\"h\", ch], [\"emoji\", \"🔥\"]] }\n// → \"kind:9002 must include at least one metadata tag (...)\"\n\n// after — include a recognized tag\n{ kind: 9002, tags: [[\"h\", ch], [\"topic\", \"Release coordination\"]] }","handlingStrategy":"validation","validationCode":"const RECOGNIZED = [\"name\", \"about\", \"archived\", \"topic\", \"purpose\", \"visibility\", \"ttl\"] as const;\n\nfunction buildMetadataEvent(channelId: string, fields: Record<string, string>) {\n  const tags: string[][] = [[\"h\", channelId]];\n  for (const [k, v] of Object.entries(fields)) {\n    if ((RECOGNIZED as readonly string[]).includes(k)) tags.push([k, v]);\n  }\n  if (tags.length === 1) throw new Error(\"kind:9002 needs at least one of: \" + RECOGNIZED.join(\", \"));\n  return { kind: 9002, tags };\n}","typeGuard":"function hasRecognizedMetadataTag(tags: string[][]): boolean {\n  const recognized = new Set([\"name\", \"about\", \"archived\", \"topic\", \"purpose\", \"visibility\", \"ttl\"]);\n  return tags.some((t) => recognized.has(t[0]));\n}","tryCatchPattern":"try {\n  await sdk.publish(editEvent);\n} catch (e) {\n  if (String(e).includes(\"must include at least one metadata tag\")) {\n    throw new Error(`No recognized field in edit — supported: name, about, archived, topic, purpose, visibility, ttl`);\n  } else throw e;\n}","preventionTips":["Type the edit payload as a partial of the seven known fields so unknown keys fail at compile time.","Keep tag kind strings lowercase and singular; reject anything else in your builder.","Add a unit test asserting every editable field your UI exposes maps to a recognized tag kind."],"tags":["nostr","validation","kind-9002","tag-validation","relay"],"backgroundTag":"missing-required-tag","analyzedSha":"f956e6fe06a76e50cbd8fba1a162482e752e7f1a","analyzedAt":"2026-08-16T22:11:40.750Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}