BoundaryML/baml · error · CtypesError

Invalid InboundValue.value_type: a root union or optional do

Error message

Invalid InboundValue.value_type: a root union or optional does not identify one exact selected type

What it means

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.

Source

Thrown at baml_language/crates/bridge_ctypes/src/error.rs:31

    #[error("Invalid handle key: {0}")]
    InvalidHandleKey(u64),

    #[error("Map entry missing key")]
    MapEntryMissingKey,

    /// Carries only the input length, not the input itself — untrusted hex
    /// blobs can be up to the FFI decode cap (~67M chars), and embedding
    /// them in error messages bloats logs and exposes payload contents.
    #[error("Invalid bigint hex string ({len} bytes)")]
    InvalidBigint { len: usize },

    /// Carries only the input length for over-cap decimal type literals, so a
    /// hostile descriptor cannot amplify logs by echoing its full payload.
    #[error("Invalid decimal bigint literal ({len} bytes)")]
    InvalidBigintLiteral { len: usize },

    #[error(
        "Invalid InboundValue.value_type: a root union or optional does not identify one exact selected type"
    )]
    InvalidInboundValueTypeRootUnion,

    #[error("Union selected type `{selected}` is not a member of declared union `{union}`")]
    UnionSelectedTypeNotMember { selected: String, union: String },

    #[error("Internal error: {0}")]
    InternalError(String),
}

View on GitHub (pinned to bd85ce9dee)

Solutions

  1. Unwrap the union/optional in the host and send the selected concrete value with its exact type.
  2. If the value is Optional::None, send the dedicated null/none inbound encoding rather than a root optional tag.
  3. Update the producer SDK so unions encode a selected member plus its index/type id.
  4. Round-trip the payload through the decoder in tests to catch root-union encodings early.

Example fix

// before
value = InboundValue { value_type: UNION_T, ... }  # root union
// after
value = InboundValue { value_type: SELECTED_MEMBER_T, union_index: Some(1), ... }
Defensive patterns

Strategy: validation

Validate before calling

def ensure_selected_inbound(v):
    if v.WhichOneof("value") in ("union", "optional") and v is root:
        raise ValueError("root inbound value must be a selected concrete type, not a union/optional")

Type guard

def is_root_union_or_optional(v) -> bool:
    t = v.value_type
    return t.is_union or t.is_optional

Try / catch

try:
    val = bridge.decode_inbound(buf, len(buf))
except BridgeError as e:
    if "root union or optional" in str(e):
        raise ValueError("unwrap the union/optional and send the selected member") from e
    raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12). Data as JSON: /api/errors/abb69da46c72bdff. Report an issue: GitHub.