{"record":{"id":"959da1bb08366d7e","repo":"block/buzz","slug":"classify-mutation-result-e","errorCode":null,"errorMessage":"classify mutation result: {e}","messagePattern":"classify mutation result: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/buzz-relay/src/handlers/report_resolution.rs","lineNumber":787,"sourceCode":"                )\n                .await\n                .map_err(|e| anyhow::anyhow!(\"delete failed: {e}\"))\n        }\n        other => Err(anyhow::anyhow!(\"unexpected enforcement action: {other}\")),\n    };\n\n    match raw? {\n        true => Ok(MutationOutcome::Committed),\n        false => {\n            // Reload to distinguish \"step_marker already set by another driver\"\n            // (AlreadyCommitted — safe to proceed to finalization) from \"this\n            // driver's lease expired\" (LeaseLost — must stop, recovery worker\n            // will take over after expiry).\n            let rec = state\n                .db\n                .get_admin_action(action_id)\n                .await\n                .map_err(|e| anyhow::anyhow!(\"classify mutation result: {e}\"))?;\n            match rec {\n                Some(r) if r.step_marker.is_some() => Ok(MutationOutcome::AlreadyCommitted),\n                _ => Ok(MutationOutcome::LeaseLost),\n            }\n        }\n    }\n}\n\n/// Decode the report target hex into binary (public for the action recovery worker).\npub type TargetPair = (Option<Vec<u8>>, Option<Vec<u8>>);\n\n/// Derive the enforcement target from a full report detail.\n///\n/// This is the single source of truth for \"who/what does enforcement act on\",\n/// shared by the HTTP driver ([`resolve_report_with_enforcement`]) and the action\n/// recovery worker (via [`derive_enforcement_target_pub`]). Because both paths\n/// derive from the same immutable report row + stored event row — and the action\n/// record persists no target columns of its own — a stranded action always","sourceCodeStart":769,"sourceCodeEnd":805,"githubUrl":"https://github.com/block/buzz/blob/eed74bde2f4797714335ac10c56c0b0244c1def4/crates/buzz-relay/src/handlers/report_resolution.rs#L769-L805","documentation":"Raised when the driver gets Ok(false) from an atomic mutation (meaning the lease fence rejected the write) and then attempts to classify the outcome by re-reading the action row with `get_admin_action`. If that classification read itself fails (DB error), this wrapper obscures whether the mutation was already committed by another driver or the lease was lost, so the driver cannot safely proceed. It exists to avoid conflating a read failure with a LeaseLost verdict.","triggerScenarios":"execute_*_with_marker returned Ok(false) (fence rejected) AND the follow-up `state.db.get_admin_action(action_id)` errors — Postgres outage, pool exhaustion, or timeout occurring in the narrow window after a contested lease.","commonSituations":"Concurrent drivers (HTTP resolve path and recovery worker) racing on the same action while the DB is under load; connection pool maxed during a moderation burst; network blip between mutation and classification read.","solutions":["Inspect the inner `{e}` for the classification read failure and restore DB connectivity / pool capacity.","Retry the whole action later — the recovery worker re-drives it after lease expiry; no partial state was written by this driver.","Add retry-with-backoff on `get_admin_action` in the classification path for transient errors.","Check for many concurrent drivers on the same action_id (lease contention) and reduce duplicate driving.","Verify action_id is valid — an invalid/corrupt action_id could also make the read fail depending on the DB layer."],"exampleFix":"// before: single read, any error aborts classification\nlet rec = state.db.get_admin_action(action_id).await\n    .map_err(|e| anyhow::anyhow!(\"classify mutation result: {e}\"))?;\n// after: small retry for transient read failures\nlet rec = retry_transient(3, || state.db.get_admin_action(action_id)).await\n    .map_err(|e| anyhow::anyhow!(\"classify mutation result: {e}\"))?;","handlingStrategy":"retry","validationCode":"// ensure DB reachable before classification\nstate.db.ping().await.map_err(|e| anyhow::anyhow!(\"classification pre-check: db unreachable: {e}\"))?;","typeGuard":"fn is_transient_read_error(e: &anyhow::Error) -> bool {\n    let s = format!(\"{e:#}\");\n    [\"timeout\", \"connection\", \"closed\", \"pool timed out\"].iter().any(|k| s.contains(k))\n}","tryCatchPattern":"// classification read with bounded retries\nlet rec = loop {\n    match state.db.get_admin_action(action_id).await {\n        Ok(r) => break Ok(r),\n        Err(e) if attempts < 3 && is_transient_read_error(&e) => { attempts += 1; sleep(BACKOFF).await; }\n        Err(e) => break Err(anyhow::anyhow!(\"classify mutation result: {e}\")),\n    }\n};","preventionTips":["Bound concurrent drivers per action_id so the Ok(false) fence path is rare.","Retry transient reads with backoff instead of treating a classification hiccup as fatal.","Remember no partial state was committed by this driver when classification fails — deferring to the recovery worker is always safe.","Size the DB pool for the moderation burst profile."],"tags":["database","concurrency","lease","race-condition","rust"],"backgroundTag":"lease-lost-concurrent-driver","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"}