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
- Match the canonical shape: public static <Return|void> Name(ProcedureContext ctx, params...) with every parameter BSATN-serializable
- Check the compiler/build output for the STDB diagnostic that names the offending procedure and fix that declaration
- Compare against a fresh template module generated with the same SDK version
- 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
- Copy reducer signatures from the SDK's current templates after every upgrade
- Keep reducers static, public, and led by ProcedureContext
- Run a dev-environment invocation of each reducer in CI
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
- Invalid HTTP handler signature.
- never types are not yet supported in C# output
- STDB0014
- Missing type name for ${typeBuilder.constructor.name ?? 'Typ
- Unsupported --dotnet-version {version}. Supported values: 8,
AI-assisted analysis of clockworklabs/SpacetimeDB@524b4487d9 (2026-08-16).
Data as JSON: /api/errors/e29e8e992f9f55f6.
Report an issue: GitHub.