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

  1. Make the enum zero-based and contiguous by removing explicit values: enum Status { Active, Inactive, Pending }
  2. If explicit values are required, keep them contiguous and within 0..N-1
  3. Validate values before serializing: reject any enum whose numeric value is outside 0..count-1
  4. 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

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


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)