BoundaryML/baml · critical
decodeUnionValue: valueUnion is nil
Error message
decodeUnionValue: valueUnion is nil
What it means
decodeUnionValue panics when handed a nil *cffi.CFFIValueUnionVariant. The decoder requires a non-nil union variant to determine the value's type; a nil pointer is an internal invariant violation in the FFI value decoding path reached through Decode.
Source
Thrown at engine/language_client_go/baml_go/serde/decode.go:226
func decodeEnumValue(valueEnum *cffi.CFFIValueEnum, typeMap TypeMap) (reflect.Value, reflect.Type) {
if valueEnum == nil {
panic("decodeEnumValue: valueEnum is nil")
}
goType, ok := typeMap.GetType(valueEnum.Name)
if !ok {
dynamicEnum := DynamicEnum{Name: valueEnum.Name.Name, Value: valueEnum.Value}
return reflect.ValueOf(dynamicEnum), reflect.TypeOf(DynamicEnum{})
}
enum := reflect.New(goType)
as_interface := enum.Interface().(BamlEnumDeserializer)
as_interface.Decode(valueEnum, typeMap)
return enum.Elem(), goType
}
func decodeUnionValue(valueUnion *cffi.CFFIValueUnionVariant, typeMap TypeMap) (reflect.Value, reflect.Type) {
if valueUnion == nil {
panic("decodeUnionValue: valueUnion is nil")
}
value, goType := func() (reflect.Value, reflect.Type) {
if ok := valueUnion.Value.GetNullValue(); ok != nil {
// If the union value is null, return nil
return reflect.ValueOf(nil), nil
} else if valueUnion.IsSinglePattern {
// For optional patterns (T | null), decode the inner value directly
// These shouldn't be looked up as union types
// Ignore the union-ness of it and just decode the inner value
return Decode(valueUnion.Value, typeMap)
} else {
goType, ok := typeMap.GetType(valueUnion.Name)
if !ok {
// Union not found
// This is a fully dynamic union, so we
// decode the value as the value and drop
// union type informationView on GitHub (pinned to bd85ce9dee)
Solutions
- Check for a nil value before calling Decode and treat it as an empty/null result
- Regenerate baml_client and align the BAML runtime version with the Go bindings
- Catch the panic at the boundary with recover() if you must survive malformed data
- File a bug with BAML including the BAML schema and payload that triggered it
Example fix
// before
val, typ := serde.Decode(value, typeMap)
// after
if value == nil || value.Value == nil {
return reflect.Value{}, nil
}
val, typ := serde.Decode(value, typeMap) Defensive patterns
Strategy: type-guard
Validate before calling
if value == nil || value.Value == nil { return reflect.Value{}, nil } Type guard
func decodable(v *cffi.CFFIValue) bool { return v != nil && v.Value != nil } Try / catch
func safeDecode(v *cffi.CFFIValue, tm serde.TypeMap) (out reflect.Value) {
defer func() { if r := recover(); r != nil { out = reflect.Value{} } }()
v2, _ := serde.Decode(v, tm)
return v2
} Prevention
- Nil-check CFFI values before Decode
- Regenerate the client when upgrading the BAML runtime
- Add recover() around serde calls processing untrusted payloads
When it happens
Trigger: Decode() on a value whose CFFI representation has a nil value union — e.g. a malformed or nil CFFIValue inside a decoded response.
Common situations: Runtime/generator version mismatch producing values the Go serde cannot interpret, corrupted native responses, or a bug in BAML's value encoding.
Related errors
- decodeCheckedValue: valueChecked is nil
- error decoding value
- panic(initErr)
- internal error: attempted to register unknown function '%s'
- failed to decode object response: %w
AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12).
Data as JSON: /api/errors/98884ea69840a948.
Report an issue: GitHub.