BoundaryML/baml · error
HostClosure cannot be serialized
Error message
HostClosure cannot be serialized
What it means
`Object::HostClosure` is a closure backed by a host (Rust) callable that cannot be represented in the Borsh wire format, so `Object`'s `BorshSerialize` fails fast with `InvalidData` when it reaches that variant. Host closures are runtime-only values and must never end up in a serialized pack or heap snapshot.
Source
Thrown at baml_language/crates/bex_vm_types/src/types/object.rs:263
v.key_ty.clone(),
v.value_ty.clone(),
v.to_index_map()
.into_iter()
.map(|(k, v)| (k.to_string(), v))
.collect(),
),
Self::Float(v) => ObjectWire::Float(*v),
Self::Future(v) => ObjectWire::Future(v.clone()),
Self::UnscheduledFuture(v) => ObjectWire::UnscheduledFuture(v.clone()),
Self::Type(v) => ObjectWire::Type(Box::new(v.ty.clone())),
Self::RustData(_) => {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"RustData cannot be serialized",
));
}
Self::HostClosure(_) => {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"HostClosure cannot be serialized",
));
}
#[cfg(feature = "heap_debug")]
Self::Sentinel(_) => {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Sentinel cannot be serialized",
));
}
};
proxy.serialize(writer)
}
}
impl BorshDeserialize for Object {
fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {View on GitHub (pinned to bd85ce9dee)
Solutions
- Exclude host closures from the serialized value graph (drop or unregister them before exporting/packing).
- Store host callables in a host-side registry and reference them from the VM by id (an `Object` variant that serializes as an integer).
- Audit pack-export input: only serialize the compiled program's object pool, never the live runtime heap.
Example fix
// before: exporting a heap where a map still holds a host callback
let pack = PackEnvelope::new(program, heap_containing_host_closure); // io::Error: HostClosure cannot be serialized
// after: keep callbacks in a registry, reference by id
let id = host_registry.insert(my_callback);
heap.store_global("on_event", Value::Int(id as i64));
let pack = PackEnvelope::new(program, heap); Defensive patterns
Strategy: validation
Validate before calling
// before pack export, scan for host callables
fn has_host_closure(v: &Value) -> bool {
matches!(v, Value::Object(o) if matches!(&*o.borrow(), Object::HostClosure(_)))
} Type guard
fn is_host_closure(o: &Object) -> bool {
matches!(o, Object::HostClosure(_))
} Prevention
- Register host callbacks in a registry and reference them by id from VM code.
- Unregister/drop host closures before any heap snapshot or pack export.
- Never store host callables in globals, maps, or arrays that feed the serialized object pool.
- Keep pack export operating on the compiled program only, not the runtime heap.
When it happens
Trigger: Serializing an `Object` (directly, or via `Value`/object-pool/pack export) holding `Self::HostClosure(_)` — typically a host-registered callback captured by VM code or stored in a reachable location at export time.
Common situations: Registering a Rust callback (e.g. for `f.cancel()` hooks or FFI bridges) that a BAML program stores in a global/map/array which is then included in pack serialization; heap-snapshot tooling run against a live session; accidental capture of host functions in compiled constants.
Understand the failure class
Background: "JSON serialization failed", "not JSON serializable", "Failed to serialize": why JSON marshaling errors happen and how to fix them — this error's family across 46 libraries.
Related errors
- RustData cannot be serialized
- invalid package interface: {message}
- variant `{}` requires an explicit discriminant to stabilize
- Future cannot be deserialized
- UnscheduledFuture cannot be serialized
AI-assisted analysis of BoundaryML/baml@bd85ce9dee (2026-09-12).
Data as JSON: /api/errors/49f2787c9ec26dc6.
Report an issue: GitHub.