{"record":{"id":"d37835dc76939451","repo":"block/buzz","slug":"ban-requires-target-pubkey","errorCode":null,"errorMessage":"ban requires target_pubkey","messagePattern":"ban requires target_pubkey","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/buzz-relay/src/handlers/report_resolution.rs","lineNumber":687,"sourceCode":"/// - [`MutationOutcome::Committed`] — this driver committed the marker.\n/// - [`MutationOutcome::AlreadyCommitted`] — another driver set the marker first.\n/// - [`MutationOutcome::LeaseLost`] — the caller's lease has expired; the caller\n///   must stop and let the recovery worker take over.\n/// - `Err` — the mutation itself failed (DB or validation error).\nasync fn run_atomic_mutation(\n    state: &Arc<AppState>,\n    action_id: Uuid,\n    lease_token: Uuid,\n    ctx: &EnforcementCtx<'_>,\n) -> anyhow::Result<MutationOutcome> {\n    // Returns Ok(true) if this driver set the marker, Ok(false) if the lease\n    // ownership fence rejected the transaction (lease lost or marker already set\n    // by a concurrent driver). We classify Ok(false) by reloading the row.\n    let raw: anyhow::Result<bool> = match ctx.action {\n        \"ban\" => {\n            let target = ctx\n                .target_pubkey\n                .ok_or_else(|| anyhow::anyhow!(\"ban requires target_pubkey\"))?;\n            state\n                .db\n                .execute_ban_with_marker(\n                    action_id,\n                    lease_token,\n                    ctx.community_id,\n                    target,\n                    ctx.actor_pubkey,\n                    ctx.reason,\n                )\n                .await\n                .map_err(|e| anyhow::anyhow!(\"ban failed: {e}\"))\n        }\n        \"timeout\" => {\n            let target = ctx\n                .target_pubkey\n                .ok_or_else(|| anyhow::anyhow!(\"timeout requires target_pubkey\"))?;\n            let until = ctx","sourceCodeStart":669,"sourceCodeEnd":705,"githubUrl":"https://github.com/block/buzz/blob/eed74bde2f4797714335ac10c56c0b0244c1def4/crates/buzz-relay/src/handlers/report_resolution.rs#L669-L705","documentation":"Inside `run_atomic_mutation` (driven by `drive_enforcement`), the 'ban' arm of the action match requires the mutation context `ctx.target_pubkey` to be `Some`. If it is `None`, the enforcement action cannot identify which user to ban, so the atomic transaction aborts with this error rather than executing a ban against an unknown target.","triggerScenarios":"An enforcement action row of type 'ban' was created (or loaded into `ActionCtx`) without its `target_pubkey` populated — e.g. the action was enqueued from a report decision that omitted the target, or the DB column was NULL when the driver leased the action.","commonSituations":"A report-resolution flow writing a ban action without resolving the reported user's pubkey; manual DB edits or migrations that inserted action rows with NULL target_pubkey; a schema/type mismatch where the target was stored in a different column than the ctx loader reads.","solutions":["Ensure the code path that enqueues the ban action always sets target_pubkey before the driver picks it up.","Query the actions table for rows of type 'ban' with NULL target_pubkey and repair or discard them.","Add a NOT NULL constraint or insert-time validation on the ban action's target_pubkey column.","Verify the ActionCtx loader maps the correct DB column into ctx.target_pubkey."],"exampleFix":"// before\nactions::insert(Action { kind: \"ban\", community_id, reason, ..Default::default() })?;\n// after\nlet target = reported_pubkey.ok_or_else(|| anyhow!(\"report has no target to ban\"))?;\nactions::insert(Action { kind: \"ban\", target_pubkey: Some(target), community_id, reason, ..Default::default() })?;","handlingStrategy":"validation","validationCode":"pub fn enqueue_ban(action_id_target: [u8; 32], community_id: i64, reason: String) -> anyhow::Result<()> {\n    anyhow::ensure!(!reason.is_empty(), \"ban requires a reason\");\n    actions::insert(Action {\n      kind: \"ban\",\n      target_pubkey: Some(action_id_target),\n      community_id,\n      reason,\n      ..Default::default()\n    })?;\n    Ok(())\n}","typeGuard":"fn ban_target(ctx: &ActionCtx) -> Option<&[u8; 32]> {\n    ctx.target_pubkey.as_ref().and_then(|b| b.try_into().ok())\n}","tryCatchPattern":"match run_atomic_mutation(&state, ctx).await {\n  Err(e) if e.to_string().contains(\"ban requires target_pubkey\") => {\n    tracing::error!(action_id = %action_id, \"malformed ban action: dropping\");\n    mark_action_failed(action_id, \"missing target_pubkey\").await?;\n  }\n  Err(e) => return Err(e),\n  Ok(v) => Ok(v),\n}","preventionTips":["Make target_pubkey NOT NULL for ban/timeout/kick action rows.","Validate actions at enqueue time, not at execution time.","Add an integration test that drives every action kind through run_atomic_mutation."],"tags":["moderation","backend","rust","missing-field","state-machine"],"backgroundTag":"missing-required-argument","analyzedSha":"eed74bde2f4797714335ac10c56c0b0244c1def4","analyzedAt":"2026-08-30T13:49:18.474Z","contentChangedAt":"2026-08-30T13:49:18.474Z","schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}