{"record":{"id":"e29e8e992f9f55f6","repo":"clockworklabs/SpacetimeDB","slug":"invalid-procedure-signature","errorCode":null,"errorMessage":"Invalid procedure signature.","messagePattern":"Invalid procedure signature\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"critical","filePath":"crates/bindings-csharp/Codegen/Module.cs","lineNumber":1727,"sourceCode":"        Scope = new Scope(methodSyntax.Parent as MemberDeclarationSyntax);\n    }\n\n    public string GenerateClass()\n    {\n        var invocationArgs =\n            Args.Length == 0 ? \"\" : \", \" + string.Join(\", \", Args.Select(a => a.Identifier));\n        var invocation = $\"{FullName}((SpacetimeDB.ProcedureContext)ctx{invocationArgs})\";\n\n        var txPayload = TxPayloadType ?? ReturnType;\n        var txPayloadIsUnit = TxPayloadIsUnit;\n\n        string[] bodyLines;\n\n        if (HasWrongSignature)\n        {\n            bodyLines =\n            [\n                \"throw new System.InvalidOperationException(\\\"Invalid procedure signature.\\\");\",\n            ];\n        }\n        else if (HasTxWrapper)\n        {\n            string[] successLines = txPayloadIsUnit\n                ? [\"return System.Array.Empty<byte>();\"]\n                :\n                [\n                    \"using var output = new MemoryStream();\",\n                    \"using var writer = new BinaryWriter(output);\",\n                    \"__txReturnRW.Write(writer, outcome.Value!);\",\n                    \"return output.ToArray();\",\n                ];\n\n            bodyLines =\n            [\n                $\"var outcome = {invocation};\",\n                \"if (!outcome.IsSuccess)\",","sourceCodeStart":1709,"sourceCodeEnd":1745,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/524b4487d949b61a07d4f39c862d1290259dfd20/crates/bindings-csharp/Codegen/Module.cs#L1709-L1745","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\npublic static void Transfer(Connection ctx, string from, string to) { } // wrong context type\n\n// after\npublic static void Transfer(ProcedureContext ctx, string from, string to) { }","handlingStrategy":"validation","validationCode":"// Signature check for CI: fail the build if a reducer method does not start with ProcedureContext.\nvar bad = typeof(Module).GetMethods(BindingFlags.Public | BindingFlags.Static)\n    .Where(m => m.GetCustomAttribute<ReducerAttribute>() is not null)\n    .Where(m => m.GetParameters().FirstOrDefault()?.ParameterType != typeof(SpacetimeDB.ProcedureContext));\nforeach (var m in bad) throw new InvalidOperationException($\"Bad reducer signature: {m.Name}\");","typeGuard":null,"tryCatchPattern":"// Dev-only smoke harness so the generated throw surfaces in CI, not production:\ntry { generatedWrapper.InvokeForTest(ctx); }\ncatch (InvalidOperationException ex) when (ex.Message == \"Invalid procedure signature.\") { Assert.Fail(ex.Message); }","preventionTips":["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"],"tags":["csharp","codegen","reducer","procedure","source-generator","spacetimedb"],"backgroundTag":"handler-signature-invalid","analyzedSha":"524b4487d949b61a07d4f39c862d1290259dfd20","analyzedAt":"2026-08-16T23:58:54.611Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}