block/buzz · error · IngestError::Rejected

visibility tag must have a value

Error message

visibility tag must have a value

What it means

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.

Source

Thrown at crates/buzz-relay/src/handlers/side_effects.rs:557

                        _ => {
                            return Err(anyhow::anyhow!("channel name is required"));
                        }
                    }
                }
            }

            // Validate visibility values before storage.
            for t in event.tags.iter() {
                if t.kind().to_string() == "visibility" {
                    match t.content() {
                        Some("open") | Some("private") => {}
                        Some(v) => {
                            return Err(anyhow::anyhow!(
                                "invalid visibility value: {v} (must be \"open\" or \"private\")"
                            ));
                        }
                        None => {
                            return Err(anyhow::anyhow!("visibility tag must have a value"));
                        }
                    }
                }
            }

            // Validate ttl values before storage. Empty string clears the TTL
            // (channel becomes permanent); any other value must parse as a
            // positive integer number of seconds. A bare tag with no value is
            // rejected so clearing is always explicit (`["ttl", ""]`).
            for t in event.tags.iter() {
                if t.kind().to_string() == "ttl" {
                    match t.content() {
                        Some("") => {}
                        Some(v) => match v.parse::<i32>() {
                            Ok(n) if n > 0 => {}
                            _ => {
                                return Err(anyhow::anyhow!(
                                    "invalid ttl value: {v} (must be a positive integer of seconds, or empty to clear)"

View on GitHub (pinned to f956e6fe06)

Solutions

  1. Emit the explicit value: ["visibility", "private"] or ["visibility", "open"].
  2. If visibility should not change, omit the visibility tag entirely.
  3. Add a payload lint for bare single-element tags before publish.

Example fix

// before
{ kind: 9002, tags: [["h", ch], ["visibility"]] }

// after
{ kind: 9002, tags: [["h", ch], ["visibility", "private"]] }
Defensive patterns

Strategy: validation

Validate before calling

// Visibility must always carry an explicit value
function buildTags(fields: { visibility?: "open" | "private" }) {
  const tags: string[][] = [["h", channelId]];
  if (fields.visibility) tags.push(["visibility", fields.visibility]); // omitted entirely when unset
  return tags;
}

Type guard

function isCompleteTag(t: string[]): boolean {
  return t.length >= 2 && typeof t[1] === "string";
}

Try / catch

try {
  await sdk.publish(editEvent);
} catch (e) {
  if (String(e).includes("visibility tag must have a value")) {
    throw new Error('Send ["visibility", "open"|"private"] or drop the tag');
  } else throw e;
}

Prevention

When it happens

Trigger: Tag arrays built as ["visibility"] without a second element, or serialization paths that drop empty-string values leaving a single-element tag.

Common situations: 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.

Related errors


AI-assisted analysis of block/buzz@f956e6fe06 (2026-08-16). Data as JSON: /api/errors/2418cf51fcd7688d. Report an issue: GitHub.