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
- Assign a positive Number (>= 1) to every FieldDescriptorProto before building the FileDescriptor.
- Fix auto-numbering logic to start at 1 and increment per message (or track the max used number).
- Stay within the valid range 1 to 536,870,911 and avoid the reserved 19000-19999 range to prevent later failures.
- 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
- Always set Number in FieldDescriptorProto initializers
- Use an allocator that starts at 1 and respects the reserved 19000-19999 range
- Validate all field numbers (1..536870911) before building descriptors
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
- Missing name.
- " " is not a valid identifier.
- Field number has already been used in " " by field " ".
- " " is already defined (as something other than a package)…
- " " is already defined in file " ".
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)