{"record":{"id":"1d68e86ce347ff08","repo":"clockworklabs/SpacetimeDB","slug":"reducer-arguments-type-typeof-t-fullname-is-not","errorCode":null,"errorMessage":"Reducer arguments type {typeof(T).FullName} is not assignable to {typeof(Reducer).FullName}.","messagePattern":"Reducer arguments type (.+?) is not assignable to (.+?)\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"sdks/csharp/src/SpacetimeDBClient.cs","lineNumber":875,"sourceCode":"\n        // Note: this method is called from unit tests.\n        internal void OnMessageReceived(byte[] bytes, DateTime timestamp)\n        {\n            _parseQueue.Add(new UnparsedMessage { bytes = bytes, timestamp = timestamp, parseQueueTrackerId = stats.ParseMessageQueueTracker.StartTrackingRequest() });\n        }\n\n        void IDbConnection.InternalCallReducer<T>(T args)\n        {\n            if (!webSocket.IsConnected)\n            {\n                Log.Error(\"Cannot call reducer, not connected to server!\");\n                return;\n            }\n\n            var requestId = stats.ReducerRequestTracker.StartTrackingRequest(args.ReducerName);\n            if (args is not Reducer typedReducer)\n            {\n                throw new InvalidOperationException(\n                    $\"Reducer arguments type {typeof(T).FullName} is not assignable to {typeof(Reducer).FullName}.\"\n                );\n            }\n\n            var encodedArgs = IStructuralReadWrite.ToBytes(args).ToList();\n            var pendingReducer = new PendingReducerCall\n            {\n                Reducer = typedReducer,\n            };\n            pendingReducerCalls[requestId] = pendingReducer;\n            webSocket.Send(new ClientMessage.CallReducer(new CallReducer(\n                requestId,\n                0, // v2 parity with Rust SDK: always CallReducerFlags::Default.\n                args.ReducerName,\n                encodedArgs\n            )));\n        }\n","sourceCodeStart":857,"sourceCodeEnd":893,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/524b4487d949b61a07d4f39c862d1290259dfd20/sdks/csharp/src/SpacetimeDBClient.cs#L857-L893","documentation":"InternalCallReducer<T> accepts any T implementing IReducerArgs (which only requires ReducerName + BSATN read/write), but the pending-call machinery stores the args as the Reducer base class so the onReducer callback can re-deliver them. If the runtime type is not a Reducer instance, the method throws InvalidOperationException after the request id was already allocated. Generated reducer wrappers always satisfy this; hand-written args types may not.","triggerScenarios":"Calling a reducer with a custom args class that implements IReducerArgs (or IStructuralReadWrite + ReducerName) but does not derive from SpacetimeDB.Reducer; invoking IDbConnection.InternalCallReducer directly with such a type; mixing args types generated against a different SDK/module version.","commonSituations":"Hand-rolling reducer argument types instead of using the generated partial classes; upgrading the module without regenerating bindings so base types diverge.","solutions":["Derive your args type from SpacetimeDB.Reducer (extend the generated partial reducer) instead of only implementing IReducerArgs","Call reducers through the generated wrappers (conn.Reducers.MyReducer(...)) rather than driving InternalCallReducer yourself","Regenerate module bindings so args types come from the same version as the client SDK"],"exampleFix":"// before\npublic readonly struct MyArgs : IReducerArgs { public string ReducerName => \"my_reducer\"; ... }\nconn.InternalCallReducer(new MyArgs()); // throws: not a Reducer\n\n// after\npublic partial struct MyReducer : SpacetimeDB.Reducer { ... }\nconn.Reducers.MyReducer(new MyArgs { ... }); // generated wrapper passes a Reducer-derived type","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"static bool IsReducerCall(object args) => args is SpacetimeDB.Reducer;","tryCatchPattern":"try { conn.Reducers.MyReducer(args); }\ncatch (InvalidOperationException e) when (e.Message.Contains(\"not assignable to\"))\n{ /* args type must derive from SpacetimeDB.Reducer — regenerate or fix the partial */ }","preventionTips":["Always call reducers through the generated wrappers","Regenerate bindings together with SDK upgrades","Never hand-implement IReducerArgs without deriving from Reducer"],"tags":["reducer","type-mismatch","bindings","csharp"],"backgroundTag":"invalid-reducer-arguments","analyzedSha":"524b4487d949b61a07d4f39c862d1290259dfd20","analyzedAt":"2026-08-16T23:58:54.611Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}