clockworklabs/SpacetimeDB · error

reducer transaction update {request_id} failed: {err}

Error message

reducer transaction update {request_id} failed: {err}

What it means

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.

Source

Thrown at crates/testing/src/modules.rs:153

            .recv_message()
            .await
            .ok_or_else(|| anyhow::anyhow!("client receiver closed before reducer update {request_id}"))?;
        let OutboundMessage::V1(SerializableMessage::TxUpdate(update)) = message else {
            anyhow::bail!("expected reducer transaction update {request_id}, got {message:?}");
        };
        let Some(event) = update.event else {
            anyhow::bail!("expected full reducer transaction update {request_id}, got light update");
        };
        if event.request_id != Some(request_id) {
            anyhow::bail!(
                "expected reducer transaction update {request_id}, got request id {:?}",
                event.request_id
            );
        }
        match &event.status {
            EventStatus::Committed(_) => Ok(()),
            EventStatus::FailedUser(err) | EventStatus::FailedInternal(err) => {
                anyhow::bail!("reducer transaction update {request_id} failed: {err}")
            }
            EventStatus::OutOfEnergy => anyhow::bail!("reducer transaction update {request_id} ran out of energy"),
        }
    }

    pub async fn read_log(&self, size: Option<u32>) -> String {
        let bytes = self
            .client
            .module()
            .database_logger()
            .tail(size, false)
            .await
            .unwrap()
            .try_collect::<BytesMut>()
            .await
            .expect("failed to collect log stream");
        String::from_utf8(bytes.into()).unwrap()
    }

View on GitHub (pinned to fb7282411b)

Solutions

  1. 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.
  2. 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.
  3. For FailedInternal, dump the module log with module.read_log(None) to find the runtime cause (panic location, constraint name).
  4. Add the missing fixture rows / context so the reducer's preconditions hold before calling it.

Example fix

// before: treats expected failure as test error
module.recv_reducer_update(rid).await?; // Err: reducer transaction update failed: `insufficient funds`
// after: assert the failure explicitly
let err = module.recv_reducer_update(rid).await.unwrap_err();
assert!(err.to_string().contains("insufficient funds"), "unexpected error: {err:#}");
Defensive patterns

Strategy: try-catch

Try / catch

// Expecting a deliberate failure: assert on the bail, don't propagate it.
let err = module.recv_reducer_update(rid).await.unwrap_err();
let msg = format!("{err:#}");
assert!(msg.contains("failed:"), "expected reducer failure, got: {msg}");
assert!(msg.contains("insufficient funds"), "unexpected reason: {msg}");

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@fb7282411b (2026-08-20). Data as JSON: /api/errors/705dcc55dd29c38d. Report an issue: GitHub.