{"record":{"id":"7cda9d793139ec5e","repo":"can1357/oh-my-pi","slug":"expected-boolean-got-typeof-value","errorCode":null,"errorMessage":"Expected boolean, got ${typeof value}","messagePattern":"Expected boolean, got (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/catalog/src/discovery/protobuf.ts","lineNumber":636,"sourceCode":"\t\tdefaultValue: undefined,\n\t\tencode(value, writer) {\n\t\t\twriter.lengthDelimited(getCodec().encode(value));\n\t\t},\n\t\tdecode(reader) {\n\t\t\treturn getCodec().decode(reader.bytes());\n\t\t},\n\t\ttoJson(value) {\n\t\t\treturn getCodec().toJson(value);\n\t\t},\n\t\tisDefault(value) {\n\t\t\treturn value === undefined;\n\t\t},\n\t};\n}\n\nfunction requireBoolean(value: unknown): boolean {\n\tif (typeof value === \"boolean\") return value;\n\tthrow new Error(`Expected boolean, got ${typeof value}`);\n}\n\nfunction requireBytes(value: unknown): Uint8Array {\n\tif (value instanceof Uint8Array) return value;\n\tthrow new Error(\"Expected Uint8Array\");\n}\n\nfunction requireNumber(value: unknown): number {\n\tif (typeof value === \"number\" && Number.isFinite(value)) return value;\n\tthrow new Error(`Expected number, got ${typeof value}`);\n}\n\nfunction requireInt32(value: unknown): number {\n\tif (typeof value === \"number\" && Number.isInteger(value)) return value | 0;\n\tthrow new Error(`Expected int32, got ${typeof value}`);\n}\nfunction requireString(value: unknown): string {\n\tif (typeof value === \"string\") return value;","sourceCodeStart":618,"sourceCodeEnd":654,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/catalog/src/discovery/protobuf.ts#L618-L654","documentation":"Thrown by requireBoolean, the validator wired into the `bool` scalar codec. `scalar()` calls `validate(value)` on every encode, toJson, and isDefault call, so this fires whenever an application-supplied field value that the descriptor declares as `kind: \"bool\"` is not a JavaScript `true`/`false` at runtime. This is an application-side type error, not a wire-format problem: the reader path never produces it because `reader.bool()` always yields a boolean. The message includes the offending value's `typeof` (e.g. \"string\", \"number\", \"undefined\") to pinpoint the mismatch.","triggerScenarios":"Specific: calling `codec.encode(msg)`/`codec(msg)` where a bool field holds a string \"true\", a number 1/0, null, or undefined for a non-optional field; passing a protobuf-JSON-style value (where booleans are real booleans but you converted them through a parser that stringified them) into encode; calling `toJson`/`isDefault` on a hand-assembled message object that skipped `codec.create()` and set fields loosely; a descriptor that declares `\"bool\"` for a field the code populates with an enum number or int flag.","commonSituations":"Real-world: form/config values read as strings (\"true\"/\"false\") passed straight into the message; JSON.parse output reused across schemas where the field changed from bool to string; migration from another protobuf library whose types were looser (`boolean | 0 | 1`); TypeScript type assertions (`as any`) hiding the mismatch until runtime.","solutions":["Coerce the value before building the message: `value === true || value === \"true\" || value === 1` (or `Boolean(value)` if truthiness semantics are acceptable).","Build messages with `codec.create({...})` so defaults fill omitted fields, and let TypeScript's inferred message type catch wrong types at compile time instead of `as any`.","Check the descriptor: if the field genuinely carries 0/1 or \"true\"/\"false\", change its ScalarKind (e.g. to \"int32\" or \"string\") rather than feeding mismatched data.","Add a boundary validation step that normalizes incoming config/JSON to strict booleans before constructing protobuf messages."],"exampleFix":"// before\nconst msg = Msg.create({ enabled: opts.enabled as any }); // opts.enabled = \"true\"\n// after\nconst msg = Msg.create({ enabled: opts.enabled === \"true\" || opts.enabled === true });","handlingStrategy":"validation","validationCode":"function isBool(v: unknown): v is boolean {\n  return typeof v === \"boolean\";\n}\n// before encode: if (!isBool(input.enabled)) throw new TypeError(\"enabled must be boolean\");","typeGuard":"function isBoolean(v: unknown): v is boolean {\n  return typeof v === \"boolean\";\n}","tryCatchPattern":"try {\n  return MyMsg.encode(value);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith(\"Expected boolean, got \")) {\n    throw new TypeError(`field '${fieldName}' requires true/false; got: ${err.message}`);\n  }\n  throw err;\n}","preventionTips":["Build messages exclusively through codec.create() so the inferred TypeScript type rejects non-booleans at compile time.","Coerce config/form inputs (\"true\"/1) to strict booleans at the ingestion boundary.","Avoid `as any`/type assertions on protobuf message fields.","Match the descriptor ScalarKind to the real data type; don't feed 0/1 flags into \"bool\" fields unconverted."],"tags":["protobuf","type-validation","encode"],"backgroundTag":"unexpected-field-type","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}