block/buzz · error

kick requires channel_id

Error message

kick requires channel_id

What it means

`run_atomic_mutation` was asked to execute a "kick" enforcement action, but `EnforcementCtx.channel_id` was `None`. A kick removes a member from a specific NIP-29 channel, so the handler cannot proceed without knowing which channel to remove the target from. This is an internal invariant failure: the enforcement-context builder (shared by the HTTP driver and the recovery worker via `drive_enforcement`) failed to populate the channel for a kick action.

Source

Thrown at crates/buzz-relay/src/handlers/report_resolution.rs:728

                .execute_timeout_with_marker(
                    action_id,
                    lease_token,
                    ctx.community_id,
                    target,
                    ctx.actor_pubkey,
                    until,
                    ctx.reason,
                )
                .await
                .map_err(|e| anyhow::anyhow!("timeout failed: {e}"))
        }
        "kick" => {
            let target = ctx
                .target_pubkey
                .ok_or_else(|| anyhow::anyhow!("kick requires target_pubkey"))?;
            let ch = ctx
                .channel_id
                .ok_or_else(|| anyhow::anyhow!("kick requires channel_id"))?;
            match state
                .db
                .execute_kick_with_marker(
                    action_id,
                    lease_token,
                    ctx.community_id,
                    ch,
                    target,
                    ctx.actor_pubkey,
                )
                .await
                .map_err(|e| anyhow::anyhow!("kick failed: {e}"))?
            {
                buzz_db::relay_admin_actions::KickWithMarkerResult::Removed => Ok(true),
                buzz_db::relay_admin_actions::KickWithMarkerResult::AlreadyMarked => Ok(false),
                buzz_db::relay_admin_actions::KickWithMarkerResult::AlreadyGone => Err(
                    anyhow::anyhow!("kick target was already absent before this action"),
                ),

View on GitHub (pinned to eed74bde2f)

Solutions

  1. Check how the report's target was decoded: ensure the reported target carries a channel (`h` tag) and that the context builder sets `ctx.channel_id` for kick actions.
  2. Guard the action builder: refuse to create a kick `EnforcementCtx` (or persist the admin action row) when the channel cannot be derived — fail earlier with a clearer message.
  3. If the data is missing, re-derive the channel from the reported event's `h` tag or thread-root metadata and re-trigger enforcement.
  4. For hand-inserted recovery rows, delete/re-create the admin action row against a report with proper channel scoping.

Example fix

// before
let ctx = EnforcementCtx {
    action: "kick",
    target_pubkey,
    channel_id: None, // kick now fails
    ..base
};
// after
let channel_id = channel_id
    .or_else(|| derive_channel_from_event(&event))
    .ok_or_else(|| anyhow::anyhow!("kick action needs a channel; report target has no h tag"))?;
let ctx = EnforcementCtx { action: "kick", target_pubkey, channel_id: Some(channel_id), ..base };
Defensive patterns

Strategy: validation

Validate before calling

fn ensure_kick_ctx(ctx: &EnforcementCtx) -> anyhow::Result<&EnforcementCtx<'_>> {
    anyhow::ensure!(ctx.action != "kick" || ctx.channel_id.is_some(),
        "kick enforcement requires a channel_id; report target must carry an h tag");
    anyhow::ensure!(ctx.action != "kick" || ctx.target_pubkey.is_some(),
        "kick enforcement requires a target_pubkey");
    Ok(ctx)
}
// call before drive_enforcement:
// ensure_kick_ctx(&ctx)?;

Type guard

fn kick_ctx_is_complete(ctx: &EnforcementCtx<'_>) -> bool {
    ctx.action != "kick"
        || (ctx.target_pubkey.is_some() && ctx.channel_id.is_some())
}

Prevention

When it happens

Trigger: 1) The report's target decode produced no channel id (e.g. target kind carries no `h`/channel tag) but was resolved with a kick. 2) A code path constructed `EnforcementCtx` manually for a kick without setting `channel_id`. 3) Legacy admin-action rows referencing reports whose channel scoping was never recorded.

Common situations: Resolving a report with kick in a deployment whose DB rows predate channel-scoped report targets; custom forks of `derive_enforcement_target_pub`/context building that forget `channel_id`; hand-written recovery tooling inserting kick action rows without a channel.

Related errors


AI-assisted analysis of block/buzz@eed74bde2f (2026-08-30). Data as JSON: /api/errors/bc9fb2780b64891c. Report an issue: GitHub.