clockworklabs/SpacetimeDB · critical · InvalidOperationException

Invalid procedure signature.

Error message

Invalid procedure signature.

What it means

When the SpacetimeDB source generator finds a reducer/procedure whose signature does not match the expected shape, it still emits a callable wrapper whose body throws InvalidOperationException("Invalid procedure signature.") on first invocation. A compiler diagnostic flags the bad declaration at build time; the runtime throw is the backstop so a mis-shaped method can never execute half-registered.

Source

Thrown at crates/bindings-csharp/Codegen/Module.cs:1727

        Scope = new Scope(methodSyntax.Parent as MemberDeclarationSyntax);
    }

    public string GenerateClass()
    {
        var invocationArgs =
            Args.Length == 0 ? "" : ", " + string.Join(", ", Args.Select(a => a.Identifier));
        var invocation = $"{FullName}((SpacetimeDB.ProcedureContext)ctx{invocationArgs})";

        var txPayload = TxPayloadType ?? ReturnType;
        var txPayloadIsUnit = TxPayloadIsUnit;

        string[] bodyLines;

        if (HasWrongSignature)
        {
            bodyLines =
            [
                "throw new System.InvalidOperationException(\"Invalid procedure signature.\");",
            ];
        }
        else if (HasTxWrapper)
        {
            string[] successLines = txPayloadIsUnit
                ? ["return System.Array.Empty<byte>();"]
                :
                [
                    "using var output = new MemoryStream();",
                    "using var writer = new BinaryWriter(output);",
                    "__txReturnRW.Write(writer, outcome.Value!);",
                    "return output.ToArray();",
                ];

            bodyLines =
            [
                $"var outcome = {invocation};",
                "if (!outcome.IsSuccess)",

View on GitHub (pinned to 524b4487d9)

Solutions

  1. Match the canonical shape: public static <Return|void> Name(ProcedureContext ctx, params...) with every parameter BSATN-serializable
  2. Check the compiler/build output for the STDB diagnostic that names the offending procedure and fix that declaration
  3. Compare against a fresh template module generated with the same SDK version
  4. Add a smoke test that invokes each reducer in a dev environment so signature problems fail in CI, not production

Example fix

// before
public static void Transfer(Connection ctx, string from, string to) { } // wrong context type

// after
public static void Transfer(ProcedureContext ctx, string from, string to) { }
Defensive patterns

Strategy: validation

Validate before calling

// Signature check for CI: fail the build if a reducer method does not start with ProcedureContext.
var bad = typeof(Module).GetMethods(BindingFlags.Public | BindingFlags.Static)
    .Where(m => m.GetCustomAttribute<ReducerAttribute>() is not null)
    .Where(m => m.GetParameters().FirstOrDefault()?.ParameterType != typeof(SpacetimeDB.ProcedureContext));
foreach (var m in bad) throw new InvalidOperationException($"Bad reducer signature: {m.Name}");

Try / catch

// Dev-only smoke harness so the generated throw surfaces in CI, not production:
try { generatedWrapper.InvokeForTest(ctx); }
catch (InvalidOperationException ex) when (ex.Message == "Invalid procedure signature.") { Assert.Fail(ex.Message); }

Prevention

When it happens

Trigger: Declaring a reducer with the wrong parameter list (missing ProcedureContext, extra non-BSATN parameters, wrong context type), making it an instance method or non-public, or returning a type that is not a valid BSATN payload.

Common situations: Upgrading the SDK where the expected context parameter changed name/type; copy-pasting handler signatures from a different bindings version; wrapping reducers in helper classes with extra DI parameters.

Related errors


AI-assisted analysis of clockworklabs/SpacetimeDB@524b4487d9 (2026-08-16). Data as JSON: /api/errors/e29e8e992f9f55f6. Report an issue: GitHub.