{"record":{"id":"f61eedb9bc46403c","repo":"BoundaryML/baml","slug":"future-cannot-be-deserialized","errorCode":null,"errorMessage":"Future cannot be deserialized","messagePattern":"Future cannot be deserialized","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"baml_language/crates/bex_vm_types/src/types/future.rs","lineNumber":130,"sourceCode":"// on `state` and the single-writer invariant enforced by the\n// `FutureManager`'s state mutex.\nunsafe impl Send for Future {}\nunsafe impl Sync for Future {}\n\n// Futures are runtime-only; they never appear in a compiled Program. Reject\n// serialization explicitly so a malformed program fails fast.\nimpl BorshSerialize for Future {\n    fn serialize<W: std::io::Write>(&self, _writer: &mut W) -> std::io::Result<()> {\n        Err(std::io::Error::new(\n            std::io::ErrorKind::InvalidData,\n            \"Future cannot be serialized\",\n        ))\n    }\n}\n\nimpl BorshDeserialize for Future {\n    fn deserialize_reader<R: std::io::Read>(_reader: &mut R) -> std::io::Result<Self> {\n        Err(std::io::Error::new(\n            std::io::ErrorKind::InvalidData,\n            \"Future cannot be deserialized\",\n        ))\n    }\n}\n\n// `UnscheduledFuture` is a runtime spawn-request slot — same lifecycle\n// shape as `Future`, never appears in a compiled `Program`. The pack\n// envelope (`baml_exec::PackEnvelope`) serializes the bytecode + the\n// constant heap; if an `UnscheduledFuture` ever reaches the serializer\n// that's a malformed program and we want to fail fast.\nimpl BorshSerialize for UnscheduledFuture {\n    fn serialize<W: std::io::Write>(&self, _writer: &mut W) -> std::io::Result<()> {\n        Err(std::io::Error::new(\n            std::io::ErrorKind::InvalidData,\n            \"UnscheduledFuture cannot be serialized\",\n        ))\n    }","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/BoundaryML/baml/blob/bd85ce9dee1463ff04d27efd20531013a4ff46c1/baml_language/crates/bex_vm_types/src/types/future.rs#L112-L148","documentation":"`bex_vm_types::types::future::Future` is a runtime-only spawn-state object (atomic state machine, cancellation token, uninitialized MaybeUninit value slot) that never appears in a compiled `Program`. Its `BorshDeserialize` impl is deliberately a stub that always returns `Err(InvalidData)`: the crate wants a malformed program (one whose constant heap or pack payload claims to contain a `Future`) to fail fast rather than produce a bogus runtime object.","triggerScenarios":"Calling `Future::deserialize_reader` / `borsh::from_reader::<Future>` or deserializing any wire structure (e.g. `ObjectWire::Future`, `Object`) whose payload routes into `Future`'s Borsh impl. There is no code path that ever returns `Ok`.","commonSituations":"Loading a corrupted or hand-crafted BAML pack (`baml_exec::PackEnvelope`) whose object pool encodes a future object; a VM/tooling bug that lets a runtime `Value::Future` leak into the constant heap that gets serialized at pack time and then deserialized on load; fuzzing or round-trip tests of the wire format.","solutions":["Fix the producer: ensure runtime `Value::Future` / spawn state never enters the serialized constant heap (check the pack-building path in `baml_exec::PackEnvelope`).","Regenerate the pack/bytecode from a trusted compiler build; the loaded artifact is malformed if it contains a future object.","If you need cross-process future state, redesign to persist an ID/handle and re-register it with the engine registry on load, not the `Future` struct itself."],"exampleFix":"// before: deserializing a heap snapshot that contains a Future\nlet heap: ConstantHeap = borsh::from_reader(&mut reader)?; // io::Error: Future cannot be deserialized\n// after: keep futures runtime-only; serialize only const-heap values\nassert!(!value.is_future(), \"futures must not enter the constant heap\");\nlet heap: ConstantHeap = borsh::from_reader(&mut reader)?;","handlingStrategy":"validation","validationCode":"// before deserializing any heap/pool payload\nfn heap_payload_contains_future(wire: &[ObjectWire]) -> bool {\n    wire.iter().any(|o| matches!(o, ObjectWire::Future(_) | ObjectWire::UnscheduledFuture(_)))\n}\n// if heap_payload_contains_future(&pool) { return Err(\"malformed pack: runtime-only value\"); }","typeGuard":"fn is_runtime_only(o: &ObjectWire) -> bool {\n    matches!(o, ObjectWire::Future(_) | ObjectWire::UnscheduledFuture(_))\n}","tryCatchPattern":null,"preventionTips":["Never feed live VM heap values into pack serialization; serialize only the compiled program's constant heap.","Treat any artifact containing a `Future` payload as corrupted — regenerate from source.","Keep spawn-state values registered with the engine registry, referenced by id rather than embedded in serializable structures.","Add a round-trip test asserting the const heap never contains runtime-only variants."],"tags":["rust","serialization","borsh","fail-fast-invariant"],"backgroundTag":"unsupported-operation","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"}