block/buzz · error · IngestError::Rejected

invalid action_id tag

Error message

invalid action_id tag

What it means

Thrown by kind:9005 (DELETE_EVENT) when an optional action_id tag is present but does not parse as a UUID. The relay validates the tag whenever it exists — an absent action_id is fine, but a malformed one is rejected before the delete proceeds.

Source

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

                    }
                }
            } else {
                // topic/purpose: any member
                let is_member = state
                    .is_member_cached(tenant.community(), channel_id, &actor_bytes)
                    .await?;
                if is_member {
                    Ok(())
                } else {
                    Err(anyhow::anyhow!("not a member"))
                }
            }
        }
        9005 => {
            // DELETE_EVENT: event author OR channel owner/admin.
            if let Some(action_id) = extract_tag_value(event, "action_id") {
                Uuid::parse_str(&action_id)
                    .map_err(|_| anyhow::anyhow!("invalid action_id tag"))?;
            }

            // Extract target event from e tag to check authorship.
            let target_id = event
                .tags
                .iter()
                .find_map(|tag| {
                    if tag.kind().to_string() == "e" {
                        tag.content().and_then(|v| hex::decode(v).ok())
                    } else {
                        None
                    }
                })
                .ok_or_else(|| anyhow::anyhow!("missing e tag for target event"))?;

            // Verify the target event exists and belongs to the h-tag channel
            // BEFORE storage. Fail closed: missing target → reject.
            let target_event = state

View on GitHub (pinned to f956e6fe06)

Solutions

  1. Omit the action_id tag if you do not need idempotency tracking — it is optional.
  2. Otherwise generate a real UUID (v4) and send it as a string: ["action_id", crypto.randomUUID()].
  3. Check for whitespace/case issues if hand-assembling; the parser is strict.

Example fix

// before
{ kind: 9005, tags: [["h", ch], ["e", targetId], ["action_id", `del-${Date.now()}`]] }

// after
{ kind: 9005, tags: [["h", ch], ["e", targetId], ["action_id", crypto.randomUUID()]] }
Defensive patterns

Strategy: validation

Validate before calling

const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;

function actionIdTag(id?: string): string[] | null {
  if (id === undefined) return null;      // omitting it is fine
  if (!UUID_RE.test(id)) throw new Error("action_id must be a UUID — got " + id);
  return ["action_id", id];
}

Type guard

function isUuid(v: string): boolean {
  return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(v);
}

Try / catch

try {
  await sdk.publish(deleteEvent);
} catch (e) {
  if (String(e).includes("invalid action_id tag")) {
    delete deleteEvent.tags.find((t) => t[0] === "action_id"); // drop the tag and retry
    await sdk.publish(deleteEvent);
  } else throw e;
}

Prevention

When it happens

Trigger: Publishing kind:9002/9005-style events with [["action_id", "delete"]], [["action_id", "12345"]], or a non-v4 string. Valid forms are UUIDs like "9b4c0d04-7a3e-4e2f-9d1a-2f6c8b5e7a11".

Common situations: Clients generating action ids with custom schemes (timestamps, nanoids, ULIDs) instead of UUIDs; copy-paste truncating the UUID; older SDK versions emitting a different id format.

Related errors


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