{"record":{"id":"bc9fb2780b64891c","repo":"block/buzz","slug":"kick-requires-channel-id","errorCode":null,"errorMessage":"kick requires channel_id","messagePattern":"kick requires channel_id","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/buzz-relay/src/handlers/report_resolution.rs","lineNumber":728,"sourceCode":"                .execute_timeout_with_marker(\n                    action_id,\n                    lease_token,\n                    ctx.community_id,\n                    target,\n                    ctx.actor_pubkey,\n                    until,\n                    ctx.reason,\n                )\n                .await\n                .map_err(|e| anyhow::anyhow!(\"timeout failed: {e}\"))\n        }\n        \"kick\" => {\n            let target = ctx\n                .target_pubkey\n                .ok_or_else(|| anyhow::anyhow!(\"kick requires target_pubkey\"))?;\n            let ch = ctx\n                .channel_id\n                .ok_or_else(|| anyhow::anyhow!(\"kick requires channel_id\"))?;\n            match state\n                .db\n                .execute_kick_with_marker(\n                    action_id,\n                    lease_token,\n                    ctx.community_id,\n                    ch,\n                    target,\n                    ctx.actor_pubkey,\n                )\n                .await\n                .map_err(|e| anyhow::anyhow!(\"kick failed: {e}\"))?\n            {\n                buzz_db::relay_admin_actions::KickWithMarkerResult::Removed => Ok(true),\n                buzz_db::relay_admin_actions::KickWithMarkerResult::AlreadyMarked => Ok(false),\n                buzz_db::relay_admin_actions::KickWithMarkerResult::AlreadyGone => Err(\n                    anyhow::anyhow!(\"kick target was already absent before this action\"),\n                ),","sourceCodeStart":710,"sourceCodeEnd":746,"githubUrl":"https://github.com/block/buzz/blob/eed74bde2f4797714335ac10c56c0b0244c1def4/crates/buzz-relay/src/handlers/report_resolution.rs#L710-L746","documentation":"`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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","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.","If the data is missing, re-derive the channel from the reported event's `h` tag or thread-root metadata and re-trigger enforcement.","For hand-inserted recovery rows, delete/re-create the admin action row against a report with proper channel scoping."],"exampleFix":"// before\nlet ctx = EnforcementCtx {\n    action: \"kick\",\n    target_pubkey,\n    channel_id: None, // kick now fails\n    ..base\n};\n// after\nlet channel_id = channel_id\n    .or_else(|| derive_channel_from_event(&event))\n    .ok_or_else(|| anyhow::anyhow!(\"kick action needs a channel; report target has no h tag\"))?;\nlet ctx = EnforcementCtx { action: \"kick\", target_pubkey, channel_id: Some(channel_id), ..base };","handlingStrategy":"validation","validationCode":"fn ensure_kick_ctx(ctx: &EnforcementCtx) -> anyhow::Result<&EnforcementCtx<'_>> {\n    anyhow::ensure!(ctx.action != \"kick\" || ctx.channel_id.is_some(),\n        \"kick enforcement requires a channel_id; report target must carry an h tag\");\n    anyhow::ensure!(ctx.action != \"kick\" || ctx.target_pubkey.is_some(),\n        \"kick enforcement requires a target_pubkey\");\n    Ok(ctx)\n}\n// call before drive_enforcement:\n// ensure_kick_ctx(&ctx)?;","typeGuard":"fn kick_ctx_is_complete(ctx: &EnforcementCtx<'_>) -> bool {\n    ctx.action != \"kick\"\n        || (ctx.target_pubkey.is_some() && ctx.channel_id.is_some())\n}","tryCatchPattern":null,"preventionTips":["Only offer \"kick\" resolutions on reports whose target includes a channel `h` tag.","Validate EnforcementCtx completeness at action-creation time, before persisting the admin action row.","Add a unit test asserting drive_enforcement never receives a kick ctx with None channel_id.","Keep derive_enforcement_target_pub as the single source of truth for target fields."],"tags":["nostr","enforcement","missing-context","internal-invariant"],"backgroundTag":"missing-enforcement-target","analyzedSha":"eed74bde2f4797714335ac10c56c0b0244c1def4","analyzedAt":"2026-08-30T13:49:18.474Z","contentChangedAt":"2026-08-30T13:49:18.474Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}