BoundaryML/baml · error

failed to unmarshal FFI response: %w

Error message

failed to unmarshal FFI response: %w

What it means

decodeAsyncResponse wraps every FFI reply from the native BAML runtime: the C buffer bytes are unmarshaled as a protobuf InvocationResponse. When the bytes are not a valid protobuf message (truncated buffer, corrupt memory, version-skewed proto schema between Go client and native library), this error wraps the proto.Unmarshal failure with %w.

Source

Thrown at engine/language_client_go/baml_go/exports.go:88

	C.WrapRegisterCallbacks((C.CallbackFn)(callbackFn), (C.CallbackFn)(errorFn), (C.OnTickCallbackFn)(onTickFn))
	return nil
}

// decodeAsyncResponse decodes a Buffer containing an InvocationResponse.
// Returns nil on success, or an error if the response contains an error.
func decodeAsyncResponse(buf C.Buffer) error {
	defer C.WrapFreeBuffer(buf)

	// Empty buffer means success (task was spawned)
	if buf.ptr == nil || buf.len == 0 {
		return nil
	}

	content_bytes := C.GoBytes(unsafe.Pointer(buf.ptr), C.int32_t(buf.len))

	var response cffi.InvocationResponse
	if err := proto.Unmarshal(content_bytes, &response); err != nil {
		return fmt.Errorf("failed to unmarshal FFI response: %w", err)
	}

	// Check if response contains an error
	switch response.GetResponse().(type) {
	case *cffi.InvocationResponse_Error:
		return fmt.Errorf("%s", response.GetError())
	default:
		// Success or nil response means success
		return nil
	}
}

func CallFunctionFromC(runtime unsafe.Pointer, functionName string, encodedArgs []byte, id uint32) error {
	cFunctionName := C.CString(functionName)
	defer C.free(unsafe.Pointer(cFunctionName))

	cEncodedArgs := (*C.char)(unsafe.Pointer(&encodedArgs[0]))

View on GitHub (pinned to bd85ce9dee)

Solutions

  1. Rebuild or re-download the BAML shared library so it matches the Go client version (delete the cached library in os.UserCacheDir()/baml and let it re-download)
  2. Check that VERSION in the Go package matches the version reported by the loaded native library (BamlVmVersion check in initializeBaml)
  3. If reproducible, report/capture the raw buffer length; a zero-length or truncated buffer points to a native-side bug
  4. Pin baml-go and the native library to identical versions in CI to avoid drift

Example fix

// before
// cached old .so downloaded months ago, mismatched with upgraded baml-go
// after
// rm -rf "$(go env GOENV-cache-dir)/baml" and rebuild so findOrDownloadLibrary fetches the matching library
Defensive patterns

Strategy: try-catch

Validate before calling

libVer, err := baml.LoadedLibraryVersion()
_ = libVer // compare against expected client version before making calls

Try / catch

resp, err := baml.CallFunctionFromC(...)
if err != nil && strings.Contains(err.Error(), "failed to unmarshal FFI response") {
    // native/Go version skew: re-download matching library and retry init
    return baml.Reinitialize()
}

Prevention

When it happens

Trigger: Any call through CallFunctionFromC, CallFunctionStreamFromC, CallFunctionParseFromC, BuildRequestFromC, or CancelFunctionCall where the returned C buffer does not decode as a cffi.InvocationResponse protobuf.

Common situations: Mixing a Go baml-go client version with a differently-built native shared library so the proto definitions differ; the native side returning an error object instead of a serialized response; memory corruption in the FFI boundary.

Understand the failure class

Background: "cannot parse invalid wire-format data", "cannot unmarshal", "failed unmarshalling": protobuf unmarshal errors explained — this error's family across 10 libraries.

Related errors


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