block/buzz · error · IngestError::Rejected
channel is archived
Error message
channel is archived
What it means
The target channel has archived_at set and the incoming event is not a kind:9002 carrying an ["archived", "false"] tag. Archived channels are frozen for mutations — PUT_USER, REMOVE_USER, metadata edits (except the unarchive edit itself), deletes, join/leave requests — so the event is rejected before storage. The only key that opens the gate is a 9002 edit whose tags include archived=false.
Source
Thrown at crates/buzz-relay/src/handlers/side_effects.rs:335
let channel_id =
extract_h_tag_channel(event).ok_or_else(|| anyhow::anyhow!("missing or invalid h tag"))?;
let actor_bytes = event.pubkey.to_bytes().to_vec();
// Reject mutations on archived channels — except kind:9002 with archived=false
// (unarchive), which must be allowed through so the channel can be restored.
let channel = state
.db
.get_channel(tenant.community(), channel_id)
.await
.map_err(|_| anyhow::anyhow!("channel not found"))?;
let is_unarchive_request = kind == 9002
&& event.tags.iter().any(|t| {
let parts = t.as_slice();
parts.len() >= 2 && parts[0] == "archived" && parts[1] == "false"
});
if channel.archived_at.is_some() && !is_unarchive_request {
return Err(anyhow::anyhow!("channel is archived"));
}
match kind {
9000 => {
// An absent role tag means "no role change requested": for an existing
// member that preserves the role they already hold, and only defaults
// to Member for a genuinely new member. Defaulting unconditionally to
// Member made a bare self-targeted PUT_USER silently demote an owner.
let role_str = extract_tag_value(event, "role");
let requested_role = match role_str {
Some(ref s) => match s.parse::<buzz_db::channel::MemberRole>() {
Ok(r) => Some(r),
Err(_) => return Err(anyhow::anyhow!("invalid role: {s}")),
},
None => None,
};
let members = state.db.get_members(tenant.community(), channel_id).await?;View on GitHub (pinned to f956e6fe06)
Solutions
- Unarchive first: publish kind 9002 with tags including ["archived", "false"], then retry the original mutation
- Or point the operation at a live channel instead of the archived one
- Surface channel.archived_at in the client UI and block admin actions on archived channels preemptively
- Check the 9002 unarchive tag is exactly ["archived", "false"] — parts[0]=="archived" && parts[1]=="false"
Example fix
// before: mutation on archived channel → "channel is archived"
EventBuilder::new(Kind::from(9000), "", [/* p, role, h */])
// after: unarchive, then mutate
EventBuilder::new(Kind::from(9002), "", [
Tag::custom(TagKind::Custom("h"), vec![channel_uuid]),
Tag::custom(TagKind::Custom("archived"), vec!["false"]),
]).to_event(&owner_keys).await?;
EventBuilder::new(Kind::from(9000), "", [/* p, role, h */]) Defensive patterns
Strategy: validation
Validate before calling
// Check archived state client-side before issuing admin events
let ch = client.get_channel(community, channel_uuid).await?;
let unarchive = kind == 9002 && event_has_tag(&event.tags, "archived", "false");
anyhow::ensure!(
ch.archived_at.is_none() || unarchive,
"channel is archived — send 9002 with [\"archived\",\"false\"] first"
); Type guard
const isUnarchive = (kind: number, tags: string[][]): boolean => kind === 9002 && tags.some(t => t[0] === "archived" && t[1] === "false");
Try / catch
match validate_admin_event(&tenant, kind, &event, &state).await {
Err(e) if e.to_string().contains("channel is archived") => {
// Deterministic: send the 9002 unarchive edit (with archived=false), then replay the mutation once
unarchive_then_retry(channel_uuid, event).await
}
other => other,
} Prevention
- Show archived state in channel UIs and disable membership/role actions there
- Automations that manage members should pre-check archived_at or self-heal via the 9002 unarchive path
- The unarchive tag is exactly ["archived", "false"] — any other spelling still counts as a frozen mutation
When it happens
Trigger: Issuing kind 9000/9001/9005/9008/9021/9022 against a channel that was archived earlier; a 9002 edit that changes name/topic without also including archived=false on an archived channel; an unarchive attempt sent as a different kind or with the tag misspelled (e.g. ["unarchive","true"]); a user re-joining an old archived channel via 9021.
Common situations: Team archives a project channel, later automation still posts membership updates to it; unarchive event built with the wrong tag value ('false' vs false, or 0); clients not surfacing archived state so users retry joins repeatedly.
Related errors
- cannot demote the last owner — transfer ownership first
- missing or invalid h tag
- channel not found
- missing p tag
- actor not authorized
AI-assisted analysis of block/buzz@f956e6fe06 (2026-08-16).
Data as JSON: /api/errors/fc7eca308c80685e.
Report an issue: GitHub.