stride3d/stride · error · InvalidOperationException

ServiceWire handshake payload is missing member '{propertyNa

Error message

ServiceWire handshake payload is missing member '{propertyName}'.

What it means

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.

Solutions

  1. 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).
  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.
  3. Check that the payload is uncompressed BinaryFormatter NRBF bytes (legacy wire used no compression); a compressor mismatch produces garbage members.
  4. Re-verify the byte stream is a complete handshake message, not truncated mid-serialization.

Example fix

// before (strict single-name match fails on renamed fields)
name => name == propertyName || name.Contains($"<{propertyName}>")

// after (case-insensitive tolerant match while debugging)
name => string.Equals(name, propertyName, StringComparison.OrdinalIgnoreCase) || name.Contains($"<{propertyName}>", StringComparison.OrdinalIgnoreCase)
Defensive patterns

Strategy: validation

Validate before calling

// Validate the handshake payload shape before trusting it
static bool LooksLikeLegacyServiceSyncInfo(ClassRecord root) {
    var names = root.MemberNames.ToHashSet(StringComparer.OrdinalIgnoreCase);
    return names.Contains("MethodInfos") && names.Contains("ServiceKeyIndex");
}

Type guard

static bool IsBinaryFormatterPayload(byte[] bytes) =>
    bytes.Length > 0 && bytes[0] == 0; // NRBF/BinaryFormatter streams start with the serialization header record; JSON payloads do not

Try / catch

try {
    handshake = ReadServiceSyncInfo(bytes);
}
catch (InvalidOperationException ex) when (ex.Message.StartsWith("ServiceWire handshake payload is missing member")) {
    log.LogError($"Handshake decode failed: {ex.Message}. Target is probably not Stride <4.2 or the wire format changed.");
    throw;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14). Data as JSON: /api/errors/5a4deb65f1ec5764. Report an issue: GitHub.

Appendix: source

Thrown at sources/launcher/Stride.Cli/Legacy/LegacyServiceWire.cs:69

                MethodName = method.GetString(Member(method, "MethodName")),
                MethodReturnType = method.GetString(Member(method, "MethodReturnType")),
                ParameterTypes = ((SZArrayRecord<string>)method.GetArrayRecord(Member(method, "ParameterTypes"))).GetArray()!,
            };
        }

        return new ServiceSyncInfo
        {
            ServiceKeyIndex = root.GetInt32(Member(root, "ServiceKeyIndex")),
            UseCompression = root.GetBoolean(Member(root, "UseCompression")),
            CompressionThreshold = root.GetInt32(Member(root, "CompressionThreshold")),
            MethodInfos = methods,
        };
    }

    // BinaryFormatter serializes auto-property backing fields ("<Name>k__BackingField"), so match by property name.
    private static string Member(ClassRecord record, string propertyName)
        => record.MemberNames.FirstOrDefault(name => name == propertyName || name.Contains($"<{propertyName}>"))
           ?? throw new InvalidOperationException($"ServiceWire handshake payload is missing member '{propertyName}'.");
}

// Stride 4.1's ServiceWire (5.3.4) did not compress the wire; pair it with the BinaryFormatter serializer above.
internal sealed class LegacyDoNothingCompressor : ICompressor
{
    public byte[] Compress(byte[] data) => data;

    public byte[] DeCompress(byte[] compressedBytes) => compressedBytes;
}

View on GitHub (pinned to 96fad776d2)