{"record":{"id":"5a4deb65f1ec5764","repo":"stride3d/stride","slug":"servicewire-handshake-payload-is-missing-member-propertyname","errorCode":null,"errorMessage":"ServiceWire handshake payload is missing member '{propertyName}'.","messagePattern":"ServiceWire handshake payload is missing member '(.+?)'\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"sources/launcher/Stride.Cli/Legacy/LegacyServiceWire.cs","lineNumber":69,"sourceCode":"                MethodName = method.GetString(Member(method, \"MethodName\")),\n                MethodReturnType = method.GetString(Member(method, \"MethodReturnType\")),\n                ParameterTypes = ((SZArrayRecord<string>)method.GetArrayRecord(Member(method, \"ParameterTypes\"))).GetArray()!,\n            };\n        }\n\n        return new ServiceSyncInfo\n        {\n            ServiceKeyIndex = root.GetInt32(Member(root, \"ServiceKeyIndex\")),\n            UseCompression = root.GetBoolean(Member(root, \"UseCompression\")),\n            CompressionThreshold = root.GetInt32(Member(root, \"CompressionThreshold\")),\n            MethodInfos = methods,\n        };\n    }\n\n    // BinaryFormatter serializes auto-property backing fields (\"<Name>k__BackingField\"), so match by property name.\n    private static string Member(ClassRecord record, string propertyName)\n        => record.MemberNames.FirstOrDefault(name => name == propertyName || name.Contains($\"<{propertyName}>\"))\n           ?? throw new InvalidOperationException($\"ServiceWire handshake payload is missing member '{propertyName}'.\");\n}\n\n// Stride 4.1's ServiceWire (5.3.4) did not compress the wire; pair it with the BinaryFormatter serializer above.\ninternal sealed class LegacyDoNothingCompressor : ICompressor\n{\n    public byte[] Compress(byte[] data) => data;\n\n    public byte[] DeCompress(byte[] compressedBytes) => compressedBytes;\n}\n","sourceCodeStart":51,"sourceCodeEnd":79,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/launcher/Stride.Cli/Legacy/LegacyServiceWire.cs#L51-L79","documentation":"ReadServiceSyncInfo decodes Stride 4.1's BinaryFormatter-produced handshake payload with the safe NRBF reader and rebuilds ServiceSyncInfo by matching member names — including auto-property backing fields serialized as \"<Name>k__BackingField\". When none of the payload's member names equals or contains the expected \"<PropertyName>\" pattern, Member throws this InvalidOperationException, meaning the decoded handshake record does not have the shape the shim expects.","triggerScenarios":"NrbfDecoder.Decode succeeds on the handshake bytes but the resulting ClassRecord lacks a member named e.g. 'MethodInfos', 'ServiceKeyIndex', 'MethodName', etc. — Member(record, propertyName) finds no FirstOrDefault match.","commonSituations":"The remote endpoint is not actually Stride 4.1 ServiceWire 5.3.4 (version mismatch — e.g. a 4.2+ server's JSON-encoded payload fed to the legacy decoder); a corrupted or truncated handshake payload; a ServiceWire version whose ServiceSyncInfo field names changed; compression applied that the LegacyDoNothingCompressor did not strip.","solutions":["Verify the target Stride project's version really is < 4.2; for 4.2+ the legacy serializer should not be installed at all (LegacyShaderCodeGenerator picks it only for version < 4.2).","Dump record.MemberNames for the received payload to see the actual field names and update the Member() name-matching accordingly if ServiceWire renamed fields.","Check that the payload is uncompressed BinaryFormatter NRBF bytes (legacy wire used no compression); a compressor mismatch produces garbage members.","Re-verify the byte stream is a complete handshake message, not truncated mid-serialization."],"exampleFix":"// before (strict single-name match fails on renamed fields)\nname => name == propertyName || name.Contains($\"<{propertyName}>\")\n\n// after (case-insensitive tolerant match while debugging)\nname => string.Equals(name, propertyName, StringComparison.OrdinalIgnoreCase) || name.Contains($\"<{propertyName}>\", StringComparison.OrdinalIgnoreCase)","handlingStrategy":"validation","validationCode":"// Validate the handshake payload shape before trusting it\nstatic bool LooksLikeLegacyServiceSyncInfo(ClassRecord root) {\n    var names = root.MemberNames.ToHashSet(StringComparer.OrdinalIgnoreCase);\n    return names.Contains(\"MethodInfos\") && names.Contains(\"ServiceKeyIndex\");\n}","typeGuard":"static bool IsBinaryFormatterPayload(byte[] bytes) =>\n    bytes.Length > 0 && bytes[0] == 0; // NRBF/BinaryFormatter streams start with the serialization header record; JSON payloads do not","tryCatchPattern":"try {\n    handshake = ReadServiceSyncInfo(bytes);\n}\ncatch (InvalidOperationException ex) when (ex.Message.StartsWith(\"ServiceWire handshake payload is missing member\")) {\n    log.LogError($\"Handshake decode failed: {ex.Message}. Target is probably not Stride <4.2 or the wire format changed.\");\n    throw;\n}","preventionTips":["Only install LegacyBinaryFormatterSerializer when the server version is strictly < 4.2.","Log record.MemberNames on failure to diagnose protocol/version drift quickly.","Keep the legacy wire uncompressed (LegacyDoNothingCompressor); any compression mismatch corrupts the decoded members."],"tags":["servicewire","nrbf","deserialization","handshake","protocol-mismatch"],"backgroundTag":"unexpected-response-shape","analyzedSha":"96fad776d210c221682aac1ccdf4c79dc046fc38","analyzedAt":"2026-09-14T02:59:31.279Z","contentChangedAt":"2026-09-14T02:59:31.279Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}