clockworklabs/SpacetimeDB · error · ArgumentOutOfRangeException
Value is out of range for enum
Error message
Value {value} is out of range for enum {typeof(T).Name} What it means
The BSATN enum writer converts the enum value to its tag byte and only writes tags below the variant count, because SpacetimeDB enums must be zero-based and contiguous. A value whose numeric tag is >= the number of variants throws ArgumentOutOfRangeException on Write.
Solutions
- Make the enum zero-based and contiguous by removing explicit values: enum Status { Active, Inactive, Pending }
- If explicit values are required, keep them contiguous and within 0..N-1
- Validate values before serializing: reject any enum whose numeric value is outside 0..count-1
- After fixing the enum, regenerate bindings so client and module agree on tags
Example fix
// before
public enum Status { Active = 1, Inactive = 2, Pending = 3 } // Pending -> tag 3 >= 3 variants -> throws
// after
public enum Status { Active, Inactive, Pending } // tags 0..2, all valid Defensive patterns
Strategy: validation
Validate before calling
static bool IsBsatnEnumValueInRange<TEnum>(TEnum value)
where TEnum : struct, Enum
{
var tag = Convert.ToInt32(value);
return tag >= 0 && tag < Enum.GetValues(typeof(TEnum)).Length;
} Type guard
static bool IsContiguousZeroBasedEnum<TEnum>() where TEnum : struct, Enum
{
var values = Enum.GetValues(typeof(TEnum)).Cast<int>().OrderBy(v => v).ToArray();
return values.Select((v, i) => v == i).All(ok => ok);
} Prevention
- Define module enums without explicit values so tags are 0..N-1
- Never cast raw ints to the enum type without range-checking
- Add a unit test asserting every variant's numeric tag is within 0..count-1
When it happens
Trigger: Serializing an enum with explicit values that are not 0..N-1 — e.g. enum Status { Active = 1, Inactive = 2, Pending = 3 } makes the last variant convert to tag 3 with only 3 valid tags (0-2). Also (Status)99 cast from a raw int.
Common situations: Porting an existing C# enum that started at 1 or reserved specific numbers for interop with other systems; casting unchecked ints to the enum type before an insert or reducer call.
Related errors
- Cannot serialize a null string as BSATN String. To…
- Tag is out of range of enum
- 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/b0595cab65a8da78.
Report an issue: GitHub.
Appendix: source
Thrown at crates/bindings-csharp/BSATN.Runtime/BSATN/Runtime.cs:172
// This was quite expensive because:
// 1. It uses reflection
// 2. It allocates
// 3. It is called on each row when decoding
//
// However, enum values are guaranteed to be sequential and zero based.
// Hence we only ever need to do an upper bound check.
// See `SpacetimeDB.Type.ParseEnum` for the syntax analysis.
//
// Note: this actually still uses reflection and allocates.
// It's hard to figure out how to avoid this without custom-generating a writer for each enum type.
var tag = Convert.ToByte(value);
if (tag < TagToValue.Length)
{
writer.Write(tag);
}
else
{
throw new ArgumentOutOfRangeException(
$"Value {value} is out of range for enum {typeof(T).Name}"
);
}
}
public AlgebraicType GetAlgebraicType(ITypeRegistrar registrar) =>
registrar.RegisterType<T>(
(_) =>
new AlgebraicType.Sum(
#pragma warning disable CA2263 // netstandard2.1 lacks the generic Enum overloads.
[
.. Enum.GetNames(typeof(T))
.Select(name => new AggregateElement(name, AlgebraicType.Unit)),
]
#pragma warning restore CA2263
)
);
}View on GitHub (pinned to 6dee26c6ef)