{"record":{"id":"5e75e518e3df3047","repo":"BoundaryML/baml","slug":"unscheduledfuture-cannot-be-serialized","errorCode":null,"errorMessage":"UnscheduledFuture cannot be serialized","messagePattern":"UnscheduledFuture cannot be serialized","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"baml_language/crates/bex_vm_types/src/types/future.rs","lineNumber":144,"sourceCode":"}\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    }\n}\n\nimpl BorshDeserialize for UnscheduledFuture {\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            \"UnscheduledFuture cannot be deserialized\",\n        ))\n    }\n}\n\n// `Future::read` calls `MaybeUninit::<Value>::assume_init_read`, which is\n// sound only because `Value: Copy`. If `Value` ever gains a non-trivial\n// `Drop` (e.g. by holding an `Arc<…>` or `Box<…>`), `assume_init_read`","sourceCodeStart":126,"sourceCodeEnd":162,"githubUrl":"https://github.com/BoundaryML/baml/blob/bd85ce9dee1463ff04d27efd20531013a4ff46c1/baml_language/crates/bex_vm_types/src/types/future.rs#L126-L162","documentation":"`UnscheduledFuture` is a runtime spawn-request slot with the same lifecycle shape as `Future` and is never baked into a compiled `Program`. Its `BorshSerialize` impl is an intentional always-fail stub: if an `UnscheduledFuture` reaches the serializer (the `baml_exec::PackEnvelope` that serializes bytecode + constant heap), the program is malformed and the library fails fast with `InvalidData`.","triggerScenarios":"Calling `borsh::to_writer`/`serialize` on an `UnscheduledFuture`, or serializing a container (`Object`, `Value`, object pool) that holds one while building a pack.","commonSituations":"A VM bug that leaks a pending spawn request into the constant heap or object pool before pack serialization; misuse of the public serialization API on live VM values; fuzz/round-trip testing of the pack format.","solutions":["Fix the pack builder so spawn-request values never enter the serialized bytecode or constant heap.","Audit the value that reached serialization (`Object::UnscheduledFuture` variant) and route it through a runtime-only path instead.","Rebuild the artifact with a correct compiler; never persist unscheduled spawn slots."],"exampleFix":"// before: packing a live heap that still holds an UnscheduledFuture\nlet pack = PackEnvelope::new(program, heap_with_spawn_slot); // io::Error: UnscheduledFuture cannot be serialized\n// after: strip runtime-only values before packing\nlet heap = heap_without_runtime_values(&live_heap); // keeps futures/spawn slots out\nlet pack = PackEnvelope::new(program, heap);","handlingStrategy":"validation","validationCode":"// before building a pack\nassert!(\n    !pool.iter().any(|o| matches!(o, Object::UnscheduledFuture(_))),\n    \"UnscheduledFuture reached the pack serializer: malformed program\"\n);","typeGuard":"fn is_unscheduled_future(v: &Value) -> bool {\n    matches!(v, Value::Object(o) if matches!(&*o.borrow(), Object::UnscheduledFuture(_)))\n}","tryCatchPattern":null,"preventionTips":["Strip runtime-only spawn slots from the heap before pack export.","Route spawn requests through the engine registry instead of the serialized object pool.","Fail at pack-build time with a clear invariant check rather than at Borsh's serializer.","Fuzz/round-trip the pack format with the invariant that runtime variants never appear."],"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"}