clockworklabs/SpacetimeDB · error · InvalidOperationException
Result failed without an error object.
Error message
Result failed without an error object.
What it means
SpacetimeDB's Result<T,E> (a TaggedEnum with OkR/ErrR variants) throws this from UnwrapOrThrow() when the value IS an ErrR but its Error payload is null, so there is no exception object to surface. The non-null Err path rethrows the stored error via `throw new Exception(e.ToString())`; the null-Err path falls through to this InvalidOperationException. In practice it means code produced Result.Err(default) or Err(null) and then called UnwrapOrThrow().
Solutions
- Audit every Result<T,E>.Err(...) construction on the failing path and pass a non-null error value (e.g. Err("reason") or Err(new MyError(...)))
- If E is nullable, prefer UnwrapOr(defaultValue) or UnwrapOrElse(_ => ...) instead of UnwrapOrThrow()
- When the Result comes from the wire, inspect the raw error payload/bytes before unwrapping to confirm the error type matches the schema
- If the Err is produced by library internals (e.g. TryWithTx), report it as a SpacetimeDB bindings bug with a repro
Example fix
// before
var result = Result<int, string>.Err(null!);
return result.UnwrapOrThrow(); // throws "Result failed without an error object."
// after - always store a real error, or unwrap without throwing
var result = Result<int, string>.Err("value out of range");
return result.UnwrapOrThrow(); // throws Exception("value out of range")
// or: int v = result.UnwrapOr(-1); Defensive patterns
Strategy: validation
Validate before calling
// Before UnwrapOrThrow, verify an Err actually carries an error object:
if (result is Result<int, string>.ErrR { Error: null })
{
// empty Err: don't unwrap, handle it here (log, default, or re-Err with a real message)
} Type guard
static bool HasThrowableError<T, E>(Result<T, E> r)
where E : class =>
r is not (Result<T, E>.ErrR { Error: null }); Try / catch
try { var v = result.UnwrapOrThrow(); }
catch (InvalidOperationException ioe) when (ioe.Message.Contains("without an error object"))
{
// the Err was constructed with a null error; fix the producer of this Result
} Prevention
- Never pass null/default to Result.Err — always a descriptive error value
- Prefer UnwrapOr/UnwrapOrElse at API boundaries where a null error is possible
- Enable nullable reference types so Err(null) on reference-typed E is a warning at the call site
When it happens
Trigger: Calling UnwrapOrThrow() on a Result<T,E> built as Result<T,E>.Err(null) or Err(default(E)) where E is a nullable/reference type; or deserializing a Result whose error payload decoded to null (e.g. ErrR over a nullable string error read from malformed BSATN bytes); or a TryWithTx-style callback returning Err(null) (see TransactionalContextState.WithTx which pipes body() results into UnwrapOrThrow).
Common situations: Module code returning Result.Ok(value)/Err(error) with a default-constructed error struct; generic helper methods that do `return Result<T,E>.Err(error!)` without a null check; deserialization of wire Results where the error type is an option/string that decoded empty; upgrading code where E changed from value type to reference type so default(E) became null.
Related errors
- Operation is not valid due to the current state of the…
- Unknown Result variant.
- Argument must be a I128
- Argument must be a I256
- Argument must be a U128
AI-assisted analysis of clockworklabs/SpacetimeDB@6dee26c6ef (2026-08-20).
Data as JSON: /api/errors/6ea70f3dfd9a382e.
Report an issue: GitHub.
Appendix: source
Thrown at crates/bindings-csharp/BSATN.Runtime/Builtins.cs:659
public TResult Match<TResult>(Func<T, TResult> onOk, Func<E, TResult> onErr) =>
this switch
{
OkR(var v) => onOk(v),
ErrR(var e) => onErr(e),
_ => throw new InvalidOperationException("Unknown Result variant."),
};
public static Result<T, E> Ok(T value) => new OkR(value);
public static Result<T, E> Err(E error) => new ErrR(error);
public T UnwrapOrThrow()
{
return this switch
{
OkR(var v) => v,
ErrR(var e) when e is not null => throw new Exception(e.ToString()),
ErrR(_) => throw new InvalidOperationException(
"Result failed without an error object."
),
_ => throw new InvalidOperationException("Unknown Result variant."),
};
}
public T UnwrapOr(T defaultValue) =>
this switch
{
OkR(var v) => v,
_ => defaultValue,
};
public T UnwrapOrElse(Func<E, T> f) =>
this switch
{
OkR(var v) => v,
ErrR(var e) => f(e),View on GitHub (pinned to 6dee26c6ef)