XINCGer/Unity3DTraining · error · DescriptorValidationException

Field numbers must be positive integers.

Error message

Field numbers must be positive integers.

What it means

FieldDescriptor construction throws DescriptorValidationException when a field's number is zero or negative. In protobuf wire format the field number is encoded together with the wire type in a varint tag; numbers must be positive integers (and by spec between 1 and 2^29 - 1, with 19000-19999 reserved), so FieldDescriptor rejects anything <= 0 immediately.

Solutions

  1. Assign a positive Number (>= 1) to every FieldDescriptorProto before building the FileDescriptor.
  2. Fix auto-numbering logic to start at 1 and increment per message (or track the max used number).
  3. Stay within the valid range 1 to 536,870,911 and avoid the reserved 19000-19999 range to prevent later failures.
  4. Validate descriptor protos before building (assert FieldNumber > 0 for each field).

Example fix

// before
var field = new FieldDescriptorProto { Name = "id", Type = FieldDescriptorProto.Types.Type.Int32 }; // Number defaults to 0
// after
var field = new FieldDescriptorProto { Name = "id", Number = 1, Type = FieldDescriptorProto.Types.Type.Int32 };
Defensive patterns

Strategy: validation

Validate before calling

static void AssertFieldNumbersPositive(DescriptorProto msg)
{
    foreach (var f in msg.Fields)
        if (f.Number <= 0)
            throw new InvalidOperationException($"Field {f.Name} in {msg.Name} has invalid number {f.Number}");
}

Type guard

bool HasValidFieldNumber(FieldDescriptorProto f) => f.Number > 0 && f.Number <= 536870911;

Try / catch

try
{
    var fd = FileDescriptor.BuildFromByteString(bytes, dependencies);
}
catch (DescriptorValidationException ex) when (ex.Message.Contains("must be positive integers"))
{
    // assign numbers and rebuild the descriptor
}

Prevention

When it happens

Trigger: Building a FileDescriptor from a FieldDescriptorProto whose Number was never set (defaults to 0) or was explicitly set to a negative value — typically a programmatically built field where Number was forgotten, since protoc never emits such values.

Common situations: Dynamic schema builders adding fields without assigning numbers; copy/pasting descriptor construction code and omitting Number; descriptors round-tripped through custom serialization that dropped the number field; auto-numbering logic starting at 0 instead of 1.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of XINCGer/Unity3DTraining@016f98412e (2026-09-12). Data as JSON: /api/errors/adcdc3017589f89d. Report an issue: GitHub.

Appendix: source

Thrown at NetWorkAndResources/Socket_Protobuff/Assets/Plugins/Google.Protobuf/Reflection/FieldDescriptor.cs:79

        /// but can be overridden using the <c>json_name</c> option in the .proto file.
        /// </summary>
        public readonly string JsonName;

        internal readonly FieldDescriptorProto Proto;

        internal FieldDescriptor(FieldDescriptorProto proto, FileDescriptor file,
                                 MessageDescriptor parent, int index, string propertyName)
            : base(file, file.ComputeFullName(parent, proto.Name), index)
        {
            Proto = proto;
            if (proto.Type != 0)
            {
                fieldType = GetFieldTypeFromProtoType(proto.Type);
            }

            if (FieldNumber <= 0)
            {
                throw new DescriptorValidationException(this, "Field numbers must be positive integers.");
            }
            ContainingType = parent;
            // OneofIndex "defaults" to -1 due to a hack in FieldDescriptor.OnConstruction.
            if (proto.OneofIndex != -1)
            {
                if (proto.OneofIndex < 0 || proto.OneofIndex >= parent.Proto.OneofDecl.Count)
                {
                    throw new DescriptorValidationException(this, "FieldDescriptorProto.oneof_index is out of range for type " + parent.Name);
                }
                ContainingOneof = parent.Oneofs[proto.OneofIndex];
            }

            file.DescriptorPool.AddSymbol(this);
            // We can't create the accessor until we've cross-linked, unfortunately, as we
            // may not know whether the type of the field is a map or not. Remember the property name
            // for later.
            // We could trust the generated code and check whether the type of the property is
            // a MapField, but that feels a tad nasty.

View on GitHub (pinned to 016f98412e)