The problem
I have trained an xgboost model using {tidymodels} and want to generate prediction intervals using int_conformal_split() to incorporate into a {vetiver} model API. I can do this successfully and generate predictions in the current R session. However if I try to save the output to disk or pin it to cloud storage with {pins}, I get an error every time I try to generate a prediction.
I presume this is an issue related to however {probably} is serializing the xgboost model object. I attempt to bundle() it first. When I do this with the fit workflow object it works correctly, but the same approach does not work with int_conformal_split().
Reproducible example
# Load required libraries
library(tidyverse)
library(tidymodels)
library(probably)
#>
#> Attaching package: 'probably'
#> The following objects are masked from 'package:base':
#>
#> as.factor, as.ordered
library(bundle)
# Prepare the data
data("penguins", package = "datasets")
penguins <- penguins |>
drop_na() |>
mutate(species = as.factor(species))
# Split the data into training and testing sets
set.seed(123)
penguins_split <- initial_split(penguins, prop = 0.8)
penguins_train <- training(penguins_split)
penguins_test <- testing(penguins_split)
# Create a recipe
penguins_rec <- recipe(body_mass ~ species + bill_len + bill_dep + flipper_len, data = penguins_train) |>
step_dummy(all_nominal_predictors()) |>
step_impute_mean(all_numeric_predictors())
# Specify the XGBoost model
xgb_spec <- boost_tree(
trees = 1000,
tree_depth = 3,
learn_rate = 0.1,
loss_reduction = 0.01,
min_n = 5
) |>
set_engine("xgboost") |>
set_mode("regression")
# Create a workflow
xgb_wf <- workflow() |>
add_recipe(penguins_rec) |>
add_model(xgb_spec)
# Fit the model
xgb_fit <- fit(xgb_wf, data = penguins_train)
# save the model to disk and reload it to generate predictions
temp_model_path <- tempfile(fileext = ".rds")
xgb_fit |>
bundle() |>
write_rds(file = temp_model_path)
xgb_fit_loaded <- read_rds(temp_model_path)
xgb_fit_loaded |>
unbundle() |>
predict(new_data = penguins_test)
#> # A tibble: 67 × 1
#> .pred
#> <dbl>
#> 1 3796.
#> 2 3788.
#> 3 4430.
#> 4 3828.
#> 5 3899.
#> 6 3967.
#> 7 3387.
#> 8 3655.
#> 9 3885.
#> 10 3728.
#> # ℹ 57 more rows
# Create conformal inference predictions
mod_int <- int_conformal_split(xgb_fit, cal_data = penguins_train)
predict(mod_int, new_data = penguins_test)
#> # A tibble: 67 × 3
#> .pred .pred_lower .pred_upper
#> <dbl> <dbl> <dbl>
#> 1 3796. 3632. 3960.
#> 2 3788. 3624. 3952.
#> 3 4430. 4266. 4594.
#> 4 3828. 3664. 3992.
#> 5 3899. 3735. 4063.
#> 6 3967. 3802. 4131.
#> 7 3387. 3223. 3552.
#> 8 3655. 3491. 3819.
#> 9 3885. 3721. 4049.
#> 10 3728. 3564. 3893.
#> # ℹ 57 more rows
# Save the conformal inference object
temp_int_path <- tempfile(fileext = ".rds")
mod_int |>
bundle() |>
write_rds(file = temp_int_path)
# Load the conformal inference object in a new session
mod_int_load <- read_rds(temp_int_path)
mod_int_load |>
unbundle() |>
predict(new_data = penguins_test)
#> Error in xgb.get.handle(object): invalid 'xgb.Booster' (blank 'externalptr').
Created on 2026-02-17 with reprex v2.1.1
Session info
sessioninfo::session_info()
#> ─ Session info ───────────────────────────────────────────────────────────────
#> setting value
#> version R version 4.5.2 (2025-10-31)
#> os macOS Tahoe 26.3
#> system aarch64, darwin20
#> ui X11
#> language (EN)
#> collate en_US.UTF-8
#> ctype en_US.UTF-8
#> tz America/New_York
#> date 2026-02-17
#> pandoc 3.6.3 @ /Applications/Positron.app/Contents/Resources/app/quarto/bin/tools/aarch64/ (via rmarkdown)
#> quarto 1.9.20 @ /usr/local/bin/quarto
#>
#> ─ Packages ───────────────────────────────────────────────────────────────────
#> package * version date (UTC) lib source
#> backports 1.5.0 2024-05-23 [2] CRAN (R 4.5.0)
#> broom * 1.0.12 2026-01-27 [1] RSPM (R 4.5.0)
#> bundle * 0.1.3 2025-12-10 [1] RSPM (R 4.5.0)
#> class 7.3-23 2025-01-01 [2] CRAN (R 4.5.2)
#> cli 3.6.5 2025-04-23 [2] CRAN (R 4.5.0)
#> codetools 0.2-20 2024-03-31 [2] CRAN (R 4.5.2)
#> data.table 1.17.8 2025-07-10 [1] RSPM (R 4.5.0)
#> dials * 1.4.2 2025-09-04 [1] RSPM (R 4.5.0)
#> DiceDesign 1.10 2023-12-07 [2] CRAN (R 4.5.0)
#> digest 0.6.37 2024-08-19 [2] CRAN (R 4.5.0)
#> dplyr * 1.1.4 2023-11-17 [2] CRAN (R 4.5.0)
#> evaluate 1.0.5 2025-08-27 [1] RSPM (R 4.5.0)
#> farver 2.1.2 2024-05-13 [2] CRAN (R 4.5.0)
#> fastmap 1.2.0 2024-05-15 [2] CRAN (R 4.5.0)
#> forcats * 1.0.0 2023-01-29 [2] CRAN (R 4.5.0)
#> fs 1.6.6 2025-04-12 [2] CRAN (R 4.5.0)
#> furrr 0.3.1 2022-08-15 [2] CRAN (R 4.5.0)
#> future 1.58.0 2025-06-05 [2] CRAN (R 4.5.0)
#> future.apply 1.20.0 2025-06-06 [2] CRAN (R 4.5.0)
#> generics 0.1.4 2025-05-09 [2] CRAN (R 4.5.0)
#> ggplot2 * 3.5.2 2025-04-09 [2] CRAN (R 4.5.0)
#> globals 0.18.0 2025-05-08 [2] CRAN (R 4.5.0)
#> glue 1.8.0 2024-09-30 [2] CRAN (R 4.5.0)
#> gower 1.0.2 2024-12-17 [2] CRAN (R 4.5.0)
#> GPfit 1.0-9 2025-04-12 [2] CRAN (R 4.5.0)
#> gtable 0.3.6 2024-10-25 [2] CRAN (R 4.5.0)
#> hardhat 1.4.2 2025-08-20 [1] RSPM (R 4.5.0)
#> hms 1.1.3 2023-03-21 [2] CRAN (R 4.5.0)
#> htmltools 0.5.8.1 2024-04-04 [2] CRAN (R 4.5.0)
#> infer * 1.1.0 2025-12-18 [1] RSPM (R 4.5.0)
#> ipred 0.9-15 2024-07-18 [2] CRAN (R 4.5.0)
#> jsonlite 2.0.0 2025-03-27 [2] CRAN (R 4.5.0)
#> knitr 1.50 2025-03-16 [2] CRAN (R 4.5.0)
#> lattice 0.22-7 2025-04-02 [2] CRAN (R 4.5.2)
#> lava 1.8.1 2025-01-12 [2] CRAN (R 4.5.0)
#> lhs 1.2.0 2024-06-30 [2] CRAN (R 4.5.0)
#> lifecycle 1.0.4 2023-11-07 [2] CRAN (R 4.5.0)
#> listenv 0.9.1 2024-01-29 [2] CRAN (R 4.5.0)
#> lubridate * 1.9.4 2024-12-08 [2] CRAN (R 4.5.0)
#> magrittr 2.0.3 2022-03-30 [2] CRAN (R 4.5.0)
#> MASS 7.3-65 2025-02-28 [2] CRAN (R 4.5.2)
#> Matrix 1.7-4 2025-08-28 [2] CRAN (R 4.5.2)
#> modeldata * 1.5.1 2025-08-22 [1] RSPM (R 4.5.0)
#> nnet 7.3-20 2025-01-01 [2] CRAN (R 4.5.2)
#> parallelly 1.45.0 2025-06-02 [2] CRAN (R 4.5.0)
#> parsnip * 1.4.1 2026-01-11 [1] RSPM (R 4.5.0)
#> pillar 1.11.0 2025-07-04 [1] RSPM (R 4.5.0)
#> pkgconfig 2.0.3 2019-09-22 [2] CRAN (R 4.5.0)
#> probably * 1.2.0 2025-10-16 [1] RSPM (R 4.5.0)
#> prodlim 2025.04.28 2025-04-28 [2] CRAN (R 4.5.0)
#> purrr * 1.1.0 2025-07-10 [1] RSPM (R 4.5.0)
#> R6 2.6.1 2025-02-15 [2] CRAN (R 4.5.0)
#> RColorBrewer 1.1-3 2022-04-03 [2] CRAN (R 4.5.0)
#> Rcpp 1.1.0 2025-07-02 [1] RSPM (R 4.5.0)
#> readr * 2.1.5 2024-01-10 [2] CRAN (R 4.5.0)
#> recipes * 1.3.1 2025-05-21 [2] CRAN (R 4.5.0)
#> reprex 2.1.1 2024-07-06 [2] CRAN (R 4.5.0)
#> rlang 1.1.6 2025-04-11 [2] CRAN (R 4.5.0)
#> rmarkdown 2.30 2025-09-28 [1] RSPM (R 4.5.0)
#> rpart 4.1.24 2025-01-07 [2] CRAN (R 4.5.2)
#> rsample * 1.3.2 2026-01-30 [1] RSPM (R 4.5.0)
#> scales * 1.4.0 2025-04-24 [2] CRAN (R 4.5.0)
#> sessioninfo 1.2.3 2025-02-05 [2] CRAN (R 4.5.0)
#> sparsevctrs 0.3.4 2025-05-25 [2] CRAN (R 4.5.0)
#> stringi 1.8.7 2025-03-27 [2] CRAN (R 4.5.0)
#> stringr * 1.6.0 2025-11-04 [1] RSPM
#> survival 3.8-3 2024-12-17 [2] CRAN (R 4.5.2)
#> tailor * 0.1.0 2025-08-25 [1] RSPM (R 4.5.0)
#> tibble * 3.3.0 2025-06-08 [2] CRAN (R 4.5.0)
#> tidymodels * 1.4.1 2025-09-08 [1] RSPM (R 4.5.0)
#> tidyr * 1.3.1 2024-01-24 [2] CRAN (R 4.5.0)
#> tidyselect 1.2.1 2024-03-11 [2] CRAN (R 4.5.0)
#> tidyverse * 2.0.0 2023-02-22 [1] RSPM (R 4.5.0)
#> timechange 0.3.0 2024-01-18 [2] CRAN (R 4.5.0)
#> timeDate 4041.110 2024-09-22 [2] CRAN (R 4.5.0)
#> tune * 2.0.1 2025-10-17 [1] RSPM (R 4.5.0)
#> tzdb 0.5.0 2025-03-15 [2] CRAN (R 4.5.0)
#> vctrs 0.6.5 2023-12-01 [2] CRAN (R 4.5.0)
#> withr 3.0.2 2024-10-28 [2] CRAN (R 4.5.0)
#> workflows * 1.3.0 2025-08-27 [1] RSPM (R 4.5.0)
#> workflowsets * 1.1.1 2025-05-27 [2] CRAN (R 4.5.0)
#> xfun 0.54 2025-10-30 [1] RSPM (R 4.5.0)
#> xgboost 3.2.0.1 2026-02-10 [1] RSPM (R 4.5.0)
#> yaml 2.3.12 2025-12-10 [1] RSPM (R 4.5.0)
#> yardstick * 1.3.2 2025-01-22 [2] CRAN (R 4.5.0)
#>
#> [1] /Users/bcs88/Library/R/arm64/4.5/library
#> [2] /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/library
#> * ── Packages attached to the search path.
#>
#> ──────────────────────────────────────────────────────────────────────────────
The problem
I have trained an xgboost model using {tidymodels} and want to generate prediction intervals using
int_conformal_split()to incorporate into a {vetiver} model API. I can do this successfully and generate predictions in the current R session. However if I try to save the output to disk or pin it to cloud storage with {pins}, I get an error every time I try to generate a prediction.I presume this is an issue related to however {probably} is serializing the xgboost model object. I attempt to
bundle()it first. When I do this with the fit workflow object it works correctly, but the same approach does not work withint_conformal_split().Reproducible example
Created on 2026-02-17 with reprex v2.1.1
Session info