XINCGer/Unity3DTraining · error · InvalidProtocolBufferException
SkipLastField called on an end-group tag, indicating that…
Error message
SkipLastField called on an end-group tag, indicating that the corresponding start-group was missing
What it means
When the pending lastTag has wire type EndGroup, SkipLastField() throws InvalidProtocolBufferException: an end-group tag means the matching start-group was closed (or never opened), so there is no field left to skip. This indicates malformed or desynchronized protobuf wire data.
Solutions
- In the tag loop, check for WireFormat.WireType.EndGroup and break/return instead of calling SkipLastField().
- Create a fresh CodedInputStream per delimited message; never continue parsing past an end-group tag.
- Verify the framing code (length prefixes vs groups) matches how the sender serialized the data.
- Catch InvalidProtocolBufferException and reject/log the payload as corrupt rather than resynchronizing.
Example fix
// before
switch (WireFormat.GetTagWireType(tag))
{
default: input.SkipLastField(); break; // EndGroup falls into default
}
// after
var wireType = WireFormat.GetTagWireType(tag);
if (wireType == WireFormat.WireType.EndGroup) return; // group closed
if (wireType != WireFormat.WireType.StartGroup) input.SkipLastField(); Defensive patterns
Strategy: try-catch
Validate before calling
if (WireFormat.GetTagWireType(tag) == WireFormat.WireType.EndGroup) return; // do not skip
Type guard
bool IsEndGroupTag(int tag) => WireFormat.GetTagWireType(tag) == WireFormat.WireType.EndGroup;
Try / catch
try { input.SkipLastField(); } catch (InvalidProtocolBufferException ex) { logger.LogError(ex, "Malformed wire data: stray end-group tag"); discardCurrentMessage(); } Prevention
- Break out of the parse loop when the wire type is EndGroup.
- Use one CodedInputStream per delimited message; do not parse across message boundaries.
- Match framing to serialization: length-prefixed senders need length-prefixed readers.
When it happens
Trigger: ReadTag() returned an end-group tag and the caller called SkipLastField() instead of treating it as the group terminator; parsing a stream whose group nesting is corrupted, or concatenating messages where a group end tag bleeds into the next parse session.
Common situations: Reusing a CodedInputStream across message boundaries in a multi-message stream (framing mismatch); hand-written parsers that don't break out of the loop on EndGroup wire type; truncated/corrupt network payloads.
Understand the failure class
Background: "cannot parse invalid wire-format data", "cannot unmarshal", "failed unmarshalling": protobuf unmarshal errors explained — this error's family across 10 libraries.
Related errors
- SkipLastField cannot be called at the end of a stream
- Mismatched end-group tag. Started with field
- Offset must be within the buffer
- Length must be non-negative and within the buffer
- Size limit must be positive
AI-assisted analysis of XINCGer/Unity3DTraining@016f98412e (2026-09-12).
Data as JSON: /api/errors/b98dffa75f2e884a.
Report an issue: GitHub.
Appendix: source
Thrown at NetWorkAndResources/Socket_Protobuff/Assets/Plugins/Google.Protobuf/CodedInputStream.cs:408
/// If a caller wishes to skip a group, they should skip the whole group, by calling this method after reading the
/// start-group tag. This behavior allows callers to call this method on any field they don't understand, correctly
/// resulting in an error if an end-group tag has not been paired with an earlier start-group tag.
/// </remarks>
/// <exception cref="InvalidProtocolBufferException">The last tag was an end-group tag</exception>
/// <exception cref="InvalidOperationException">The last read operation read to the end of the logical stream</exception>
public void SkipLastField()
{
if (lastTag == 0)
{
throw new InvalidOperationException("SkipLastField cannot be called at the end of a stream");
}
switch (WireFormat.GetTagWireType(lastTag))
{
case WireFormat.WireType.StartGroup:
SkipGroup(lastTag);
break;
case WireFormat.WireType.EndGroup:
throw new InvalidProtocolBufferException("SkipLastField called on an end-group tag, indicating that the corresponding start-group was missing");
case WireFormat.WireType.Fixed32:
ReadFixed32();
break;
case WireFormat.WireType.Fixed64:
ReadFixed64();
break;
case WireFormat.WireType.LengthDelimited:
var length = ReadLength();
SkipRawBytes(length);
break;
case WireFormat.WireType.Varint:
ReadRawVarint32();
break;
}
}
private void SkipGroup(uint startGroupTag)
{View on GitHub (pinned to 016f98412e)