{"record":{"id":"c1a429bcf0ff9fb8","repo":"block/buzz","slug":"timeout-requires-timeout-until","errorCode":null,"errorMessage":"timeout requires timeout_until","messagePattern":"timeout requires timeout_until","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/buzz-relay/src/handlers/report_resolution.rs","lineNumber":707,"sourceCode":"                .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\n                .timeout_until\n                .ok_or_else(|| anyhow::anyhow!(\"timeout requires timeout_until\"))?;\n            state\n                .db\n                .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\"))?;","sourceCodeStart":689,"sourceCodeEnd":725,"githubUrl":"https://github.com/block/buzz/blob/eed74bde2f4797714335ac10c56c0b0244c1def4/crates/buzz-relay/src/handlers/report_resolution.rs#L689-L725","documentation":"The 'timeout' arm of `run_atomic_mutation` requires `ctx.timeout_until` to be `Some` — a timeout must know when it expires. If the context carries a target but no expiry timestamp, the driver aborts with this error, because executing a timeout without an `until` value would leave the user timed out indefinitely or produce an invalid DB row.","triggerScenarios":"A timeout enforcement action was created without its expiry timestamp: the decision handler omitted `timeout_until`, the DB column was NULL when leased, or the timestamp was lost in serialization between enqueue and drive.","commonSituations":"Report-resolution UI/flow allowing a timeout decision with an empty duration field; JSON serialization dropping the timestamp (None vs missing key); manual action rows inserted during testing without expiry; migration or schema change renaming the expiry column.","solutions":["Set `timeout_until` when enqueuing the timeout action (compute from now + duration).","Audit the actions table for 'timeout' rows with NULL timeout_until and repair or drop them.","Validate the timeout duration input in the decision flow before creating the action.","Check serialization between the producer and driver so the timestamp is never dropped (e.g. require the field in the payload type)."],"exampleFix":"// before\nactions::insert(Action { kind: \"timeout\", target_pubkey: Some(t), .. })?;\n// after\nlet until = chrono::Utc::now() + chrono::Duration::seconds(duration_secs);\nactions::insert(Action { kind: \"timeout\", target_pubkey: Some(t), timeout_until: Some(until), .. })?;","handlingStrategy":"validation","validationCode":"let until = ctx.timeout_until.ok_or_else(|| {\n  anyhow::anyhow!(\"cannot enqueue timeout: expiry timestamp missing\")\n})?;\nanyhow::ensure!(until > chrono::Utc::now(), \"timeout expiry must be in the future\");","typeGuard":"fn timeout_expiry(ctx: &ActionCtx) -> Option<chrono::DateTime<chrono::Utc>> {\n  ctx.timeout_until\n}","tryCatchPattern":"match run_atomic_mutation(&state, ctx).await {\n  Err(e) if e.to_string().contains(\"timeout requires timeout_until\") => {\n    tracing::error!(action_id = %action_id, \"timeout action missing expiry: dropping\");\n    mark_action_failed(action_id, \"missing timeout_until\").await?;\n  }\n  Err(e) => return Err(e),\n  Ok(v) => Ok(v),\n}","preventionTips":["Make timeout_until NOT NULL for timeout actions in the schema.","Require the duration field in the decision UI/API before creating the action.","Beware None-vs-missing-key JSON serialization when passing the action between services.","Test the timeout path with the expiry field explicitly set and unset."],"tags":["moderation","backend","rust","missing-field","timestamp"],"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"}