{"record":{"id":"2418cf51fcd7688d","repo":"block/buzz","slug":"visibility-tag-must-have-a-value","errorCode":null,"errorMessage":"visibility tag must have a value","messagePattern":"visibility tag must have a value","errorType":"validation","errorClass":"IngestError::Rejected","httpStatus":400,"severity":"error","filePath":"crates/buzz-relay/src/handlers/side_effects.rs","lineNumber":557,"sourceCode":"                        _ => {\n                            return Err(anyhow::anyhow!(\"channel name is required\"));\n                        }\n                    }\n                }\n            }\n\n            // Validate visibility values before storage.\n            for t in event.tags.iter() {\n                if t.kind().to_string() == \"visibility\" {\n                    match t.content() {\n                        Some(\"open\") | Some(\"private\") => {}\n                        Some(v) => {\n                            return Err(anyhow::anyhow!(\n                                \"invalid visibility value: {v} (must be \\\"open\\\" or \\\"private\\\")\"\n                            ));\n                        }\n                        None => {\n                            return Err(anyhow::anyhow!(\"visibility tag must have a value\"));\n                        }\n                    }\n                }\n            }\n\n            // Validate ttl values before storage. Empty string clears the TTL\n            // (channel becomes permanent); any other value must parse as a\n            // positive integer number of seconds. A bare tag with no value is\n            // rejected so clearing is always explicit (`[\"ttl\", \"\"]`).\n            for t in event.tags.iter() {\n                if t.kind().to_string() == \"ttl\" {\n                    match t.content() {\n                        Some(\"\") => {}\n                        Some(v) => match v.parse::<i32>() {\n                            Ok(n) if n > 0 => {}\n                            _ => {\n                                return Err(anyhow::anyhow!(\n                                    \"invalid ttl value: {v} (must be a positive integer of seconds, or empty to clear)\"","sourceCodeStart":539,"sourceCodeEnd":575,"githubUrl":"https://github.com/block/buzz/blob/f956e6fe06a76e50cbd8fba1a162482e752e7f1a/crates/buzz-relay/src/handlers/side_effects.rs#L539-L575","documentation":"Thrown when a kind:9002 event has a bare [\"visibility\"] tag with no value element. Visibility changes must state the target value explicitly; the relay will not guess a default.","triggerScenarios":"Tag arrays built as [\"visibility\"] without a second element, or serialization paths that drop empty-string values leaving a single-element tag.","commonSituations":"Toggle helpers that emit the flag name only when the desired state is falsy; defensive payload trimming that removes empty strings; hand-crafted events in tests.","solutions":["Emit the explicit value: [\"visibility\", \"private\"] or [\"visibility\", \"open\"].","If visibility should not change, omit the visibility tag entirely.","Add a payload lint for bare single-element tags before publish."],"exampleFix":"// before\n{ kind: 9002, tags: [[\"h\", ch], [\"visibility\"]] }\n\n// after\n{ kind: 9002, tags: [[\"h\", ch], [\"visibility\", \"private\"]] }","handlingStrategy":"validation","validationCode":"// Visibility must always carry an explicit value\nfunction buildTags(fields: { visibility?: \"open\" | \"private\" }) {\n  const tags: string[][] = [[\"h\", channelId]];\n  if (fields.visibility) tags.push([\"visibility\", fields.visibility]); // omitted entirely when unset\n  return tags;\n}","typeGuard":"function isCompleteTag(t: string[]): boolean {\n  return t.length >= 2 && typeof t[1] === \"string\";\n}","tryCatchPattern":"try {\n  await sdk.publish(editEvent);\n} catch (e) {\n  if (String(e).includes(\"visibility tag must have a value\")) {\n    throw new Error('Send [\"visibility\", \"open\"|\"private\"] or drop the tag');\n  } else throw e;\n}","preventionTips":["Build tags from a typed options object where undefined means 'omit the tag'.","Lint publish payloads for any tag array of length < 2.","Keep display labels ('Private') separate from wire values ('private')."],"tags":["nostr","validation","kind-9002","missing-tag-value","relay"],"backgroundTag":"missing-tag-value","analyzedSha":"f956e6fe06a76e50cbd8fba1a162482e752e7f1a","analyzedAt":"2026-08-16T22:11:40.750Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}