can1357/oh-my-pi · error · Error
Unknown oneof field ${fieldNumber}
Error message
Unknown oneof field ${fieldNumber} What it means
Thrown by the compiled oneof decoder in compileOneofField when an incoming protobuf message contains a tag whose field number does not map to any variant declared for that oneof. The oneof handler builds two maps from the descriptor (variantsByName, variantsByNumber) at compile time; during decode, `variantsByNumber.get(fieldNumber)` returned undefined, so the wire carries a field number this schema never declared. Unlike unknown top-level fields (which are captured in `$unknown` for forward-compatible round-trips), an unrecognized number inside the oneof's declared number range or one misrouted to the oneof handler is treated as a schema violation and thrown. It means the binary payload and the descriptor IR disagree.
Source
Thrown at packages/catalog/src/discovery/protobuf.ts:465
return {
number: 0,
initDefault(message) {
Reflect.set(message, name, { case: undefined });
},
encode(message, writer) {
const oneof = Reflect.get(message, name);
if (!oneof || typeof oneof !== "object" || !("case" in oneof) || typeof oneof.case !== "string") return;
const variant = variantsByName.get(oneof.case);
if (!variant) return;
const value = Reflect.get(oneof, "value");
if (value === undefined) return;
writer.tag(variant.no, variant.codec.wireType);
variant.codec.encode(value, writer);
},
decode(message, reader, wireType, fieldNumber) {
const variant = variantsByNumber.get(fieldNumber);
if (!variant) throw new Error(`Unknown oneof field ${fieldNumber}`);
assertWireType(wireType, variant.codec.wireType);
Reflect.set(message, name, { case: variant.name, value: variant.codec.decode(reader) });
},
toJson(message, output) {
const oneof = Reflect.get(message, name);
if (!oneof || typeof oneof !== "object" || !("case" in oneof) || typeof oneof.case !== "string") return;
const variant = variantsByName.get(oneof.case);
if (!variant) return;
const value = Reflect.get(oneof, "value");
if (value === undefined) return;
output[oneof.case] = variant.codec.toJson(value);
},
};
}
function isPackableScalar(value: ValueCodec<unknown>): boolean {
return value.wireType === 0 || value.wireType === 1 || value.wireType === 5;
}View on GitHub (pinned to 9690622007)
Solutions
- Log the offending fieldNumber and compare it against the `no` values in the OneofFieldDesc — identify which variant is missing and add it to `desc.variants`.
- Regenerate or update the descriptor IR from the current .proto so sender and receiver agree on oneof field numbers.
- If the number should be treated as unknown rather than fatal, skip the field instead of throwing (mirror the top-level unknown-field handling that populates `$unknown`), but only if your contract allows dropping it.
- Verify the decoder dispatch isn't routing another field's tag to this oneof handler (off-by-one in the compiled field-number table).
Example fix
// before (descriptor missing the newer variant)
kind: "oneof",
name: "result",
variants: [
{ no: 1, name: "text", kind: "string" },
]
// after (add the arm the sender now emits)
variants: [
{ no: 1, name: "text", kind: "string" },
{ no: 2, name: "payload", kind: "message", T: () => PayloadRef },
] Defensive patterns
Strategy: try-catch
Validate before calling
// Confirm every field number on the wire is declared before decoding:
// (schema-side check) ensure each oneof variant has a unique, expected `no`
const declared = new Set(desc.variants.map(v => v.no));
if (!declared.has(expectedIncomingFieldNo)) {
throw new Error(`descriptor does not declare oneof field ${expectedIncomingFieldNo}; update the schema`);
} Try / catch
let msg;
try {
msg = MyMsg.decode(bytes);
} catch (err) {
if (err instanceof Error && /^Unknown oneof field \d+$/.test(err.message)) {
// schema lag: log the field number, request upgrade, or fall back to raw payload
logger.warn("peer sent undeclared oneof field; refresh descriptors", { err: err.message });
return null; // or re-request from sender
}
throw err;
} Prevention
- Regenerate descriptor IR from the .proto in CI on every schema change so sender and receiver never drift.
- Version your wire payloads (envelope field or content-type version) and gate decode on a compatible schema version.
- Keep oneof field numbers stable in the .proto; reserve removed numbers with `reserved`.
- Add a round-trip test: encode with the latest schema, decode with the deployed schema, and fail CI on 'Unknown oneof field'.
When it happens
Trigger: Specific conditions: (1) decoding wire bytes where the tag's field number was not registered via `variantsByNumber.set(variant.no, ...)` in the OneofFieldDesc — e.g. a variant missing from `desc.variants`; (2) sender and receiver built from different schema revisions (sender added a new oneof variant, receiver's descriptor predates it); (3) a descriptor typo where the variant's `no` doesn't match the .proto; (4) a caller hand-crafting wire bytes or dispatching a field number to the wrong compiled field handler. The call path is oneofHandler -> compileOneofField.decode(message, reader, wireType, fieldNumber) at protobuf.ts:465.
Common situations: Real-world: a service deployed a newer .proto with an added oneof arm while the client still bundles the old generated descriptor; a duplicated or shifted field number after renumbering fields in the .proto; a vendor API updated server-side and the pinned descriptor in this library is stale; hand-rolled fuzz/test bytes hitting the oneof handler.
Related errors
- Unsupported protobuf wire type ${wireType} at byte ${reader.
- Unsupported wire type ${entryWireType} in map entry
- Expected boolean, got ${typeof value}
- Expected Uint8Array
- Expected number, got ${typeof value}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/fa59d727522afe42.
Report an issue: GitHub.