{"record":{"id":"abb69da46c72bdff","repo":"BoundaryML/baml","slug":"invalid-inboundvalue-value-type-a-root-union-or-optional","errorCode":null,"errorMessage":"Invalid InboundValue.value_type: a root union or optional does not identify one exact selected type","messagePattern":"Invalid InboundValue\\.value_type: a root union or optional does not identify one exact selected type","errorType":"error_code","errorClass":"CtypesError","httpStatus":null,"severity":"error","filePath":"baml_language/crates/bridge_ctypes/src/error.rs","lineNumber":31,"sourceCode":"\n    #[error(\"Invalid handle key: {0}\")]\n    InvalidHandleKey(u64),\n\n    #[error(\"Map entry missing key\")]\n    MapEntryMissingKey,\n\n    /// Carries only the input length, not the input itself — untrusted hex\n    /// blobs can be up to the FFI decode cap (~67M chars), and embedding\n    /// them in error messages bloats logs and exposes payload contents.\n    #[error(\"Invalid bigint hex string ({len} bytes)\")]\n    InvalidBigint { len: usize },\n\n    /// Carries only the input length for over-cap decimal type literals, so a\n    /// hostile descriptor cannot amplify logs by echoing its full payload.\n    #[error(\"Invalid decimal bigint literal ({len} bytes)\")]\n    InvalidBigintLiteral { len: usize },\n\n    #[error(\n        \"Invalid InboundValue.value_type: a root union or optional does not identify one exact selected type\"\n    )]\n    InvalidInboundValueTypeRootUnion,\n\n    #[error(\"Union selected type `{selected}` is not a member of declared union `{union}`\")]\n    UnionSelectedTypeNotMember { selected: String, union: String },\n\n    #[error(\"Internal error: {0}\")]\n    InternalError(String),\n}\n","sourceCodeStart":13,"sourceCodeEnd":42,"githubUrl":"https://github.com/BoundaryML/baml/blob/bd85ce9dee1463ff04d27efd20531013a4ff46c1/baml_language/crates/bridge_ctypes/src/error.rs#L13-L42","documentation":"CtypesError::InvalidInboundValueTypeRootUnion is thrown when decoding an InboundValue whose value_type identifies a root union (or optional) instead of exactly one selected concrete type. At the bridge boundary the decoder needs to know which single alternative was chosen; a root-level union/optional tag gives it nothing to reconstruct.","triggerScenarios":"Encoding an inbound argument whose top-level type is a union or optional and sending it without selecting the concrete member — i.e. value_type set to a union/optional descriptor rather than the selected variant's type.","commonSituations":"Hand-built InboundValues in tools/tests where the caller forgot to unwrap an Optional; SDKs serializing an unselected union; schema changes making the root type a union while callers still send the old flat shape.","solutions":["Unwrap the union/optional in the host and send the selected concrete value with its exact type.","If the value is Optional::None, send the dedicated null/none inbound encoding rather than a root optional tag.","Update the producer SDK so unions encode a selected member plus its index/type id.","Round-trip the payload through the decoder in tests to catch root-union encodings early."],"exampleFix":"// before\nvalue = InboundValue { value_type: UNION_T, ... }  # root union\n// after\nvalue = InboundValue { value_type: SELECTED_MEMBER_T, union_index: Some(1), ... }","handlingStrategy":"validation","validationCode":"def ensure_selected_inbound(v):\n    if v.WhichOneof(\"value\") in (\"union\", \"optional\") and v is root:\n        raise ValueError(\"root inbound value must be a selected concrete type, not a union/optional\")","typeGuard":"def is_root_union_or_optional(v) -> bool:\n    t = v.value_type\n    return t.is_union or t.is_optional","tryCatchPattern":"try:\n    val = bridge.decode_inbound(buf, len(buf))\nexcept BridgeError as e:\n    if \"root union or optional\" in str(e):\n        raise ValueError(\"unwrap the union/optional and send the selected member\") from e\n    raise","preventionTips":["Always unwrap optionals and select a union member before serializing inbound args","Encode Optional::None via the dedicated none encoding, not a root optional tag","Add round-trip tests whenever a root argument type becomes a union"],"tags":["protobuf","union","decoding","ffi"],"backgroundTag":"schema-validation-failed","analyzedSha":"bd85ce9dee1463ff04d27efd20531013a4ff46c1","analyzedAt":"2026-09-12T03:38:25.718Z","contentChangedAt":"2026-09-12T03:38:25.718Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}