{"record":{"id":"705dcc55dd29c38d","repo":"clockworklabs/SpacetimeDB","slug":"reducer-transaction-update-request-id-failed-e","errorCode":null,"errorMessage":"reducer transaction update {request_id} failed: {err}","messagePattern":"reducer transaction update (.+?) failed: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/testing/src/modules.rs","lineNumber":153,"sourceCode":"            .recv_message()\n            .await\n            .ok_or_else(|| anyhow::anyhow!(\"client receiver closed before reducer update {request_id}\"))?;\n        let OutboundMessage::V1(SerializableMessage::TxUpdate(update)) = message else {\n            anyhow::bail!(\"expected reducer transaction update {request_id}, got {message:?}\");\n        };\n        let Some(event) = update.event else {\n            anyhow::bail!(\"expected full reducer transaction update {request_id}, got light update\");\n        };\n        if event.request_id != Some(request_id) {\n            anyhow::bail!(\n                \"expected reducer transaction update {request_id}, got request id {:?}\",\n                event.request_id\n            );\n        }\n        match &event.status {\n            EventStatus::Committed(_) => Ok(()),\n            EventStatus::FailedUser(err) | EventStatus::FailedInternal(err) => {\n                anyhow::bail!(\"reducer transaction update {request_id} failed: {err}\")\n            }\n            EventStatus::OutOfEnergy => anyhow::bail!(\"reducer transaction update {request_id} ran out of energy\"),\n        }\n    }\n\n    pub async fn read_log(&self, size: Option<u32>) -> String {\n        let bytes = self\n            .client\n            .module()\n            .database_logger()\n            .tail(size, false)\n            .await\n            .unwrap()\n            .try_collect::<BytesMut>()\n            .await\n            .expect(\"failed to collect log stream\");\n        String::from_utf8(bytes.into()).unwrap()\n    }","sourceCodeStart":135,"sourceCodeEnd":171,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/fb7282411b0b73cc68d51e161f180cd409abd851/crates/testing/src/modules.rs#L135-L171","documentation":"In recv_reducer_update (crates/testing/src/modules.rs:153), a transaction update that carries the correct request_id is inspected for its EventStatus. FailedUser means the module code itself returned an Err from the reducer; FailedInternal means the host/runtime aborted the reducer (panic, unsupported operation, constraint violation at the runtime level). The {err} payload is the reducer's error message or the internal failure reason, so the transaction did not commit.","triggerScenarios":"A test calls a reducer that executes `return Err(...)` in module code; a reducer hits a unique constraint or precondition and errors intentionally; the module panics or performs an unsupported host call, yielding FailedInternal; test fixtures missing rows the reducer requires.","commonSituations":"Asserting success on reducers designed to fail (validation paths); forgetting fixture setup so a reducer's precondition check errors; FailedInternal after upgrading the module SDK where a host API changed behavior.","solutions":["Read the {err} text: for FailedUser it is exactly the error your reducer returned - fix the module logic or the test input that violates the precondition.","If the failure is expected, assert on it instead of using ? on recv_reducer_update: match the error and check the message, or consume the update yourself and match EventStatus::FailedUser.","For FailedInternal, dump the module log with module.read_log(None) to find the runtime cause (panic location, constraint name).","Add the missing fixture rows / context so the reducer's preconditions hold before calling it."],"exampleFix":"// before: treats expected failure as test error\nmodule.recv_reducer_update(rid).await?; // Err: reducer transaction update failed: `insufficient funds`\n// after: assert the failure explicitly\nlet err = module.recv_reducer_update(rid).await.unwrap_err();\nassert!(err.to_string().contains(\"insufficient funds\"), \"unexpected error: {err:#}\");","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Expecting a deliberate failure: assert on the bail, don't propagate it.\nlet err = module.recv_reducer_update(rid).await.unwrap_err();\nlet msg = format!(\"{err:#}\");\nassert!(msg.contains(\"failed:\"), \"expected reducer failure, got: {msg}\");\nassert!(msg.contains(\"insufficient funds\"), \"unexpected reason: {msg}\");","preventionTips":["Seed all fixture rows a reducer needs before calling it; most FailedUser errors are precondition checks.","For reducers that may legitimately fail, assert on the failure text instead of using `?` on recv_reducer_update.","On unexpected FailedInternal, dump module.read_log(None) first - the panic/host cause is in the module log, not the error."],"tags":["spacetimedb","testing","reducer","transaction-failed"],"backgroundTag":"reducer-transaction-failed","analyzedSha":"fb7282411b0b73cc68d51e161f180cd409abd851","analyzedAt":"2026-08-20T06:08:37.179Z","contentChangedAt":"2026-08-20T06:08:37.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}