An R package library can accumulate older releases while CRAN publishes compatible replacements. Checking the repository comparison first makes the available version change visible before any installed package is replaced.
R's old.packages() function compares one installed library with the selected repositories, and update.packages() can restrict installation to rows chosen from that result. This path is intended for repository-managed packages in a regular library, not dependencies controlled by an renv lockfile.
The jsonlite update uses the first active library and copies its installed directory before the change. Substitute a package shown by the comparison, keep the update confined to its reported library, and run a package-specific smoke test after confirming that no selected update remains pending.
$ R --quiet --no-save >
> options(repos = c(CRAN = "https://cloud.r-project.org"))
> package_lib <- .libPaths()[1]
> package_lib [1] "/home/analyst/R/library"
The path differs by account and operating system, and it must identify the writable library intended for the update.
Related: Set a user package library in R
> updates <- old.packages(lib.loc = package_lib, repos = getOption("repos"))
> updates[, c("Package", "Installed", "ReposVer"), drop = FALSE]
Package Installed ReposVer
jsonlite "jsonlite" "1.8.9" "2.0.0"
A NULL result means that the selected repository has no suitable newer release for a package in this library.
> selected <- updates[rownames(updates) %in% "jsonlite", , drop = FALSE]
The selection may contain one or more package names from the preceding result.
> selected[, c("Package", "LibPath", "Installed", "ReposVer"), drop = FALSE]
Package LibPath Installed ReposVer
jsonlite "jsonlite" "/home/analyst/R/library" "1.8.9" "2.0.0"
> backup_dir <- path.expand("~/R/package-backup-before-update")
> dir.create(backup_dir, recursive = TRUE, showWarnings = FALSE)
> file.copy(file.path(package_lib, rownames(selected)), backup_dir, recursive = TRUE) [1] TRUE
Any FALSE result means the backup is incomplete and the previous package version is not protected. A successful update replaces the installed directory, so this copy is the local rollback source if the new release breaks the workload.
> update.packages(lib.loc = package_lib, repos = getOption("repos"), oldPkgs = selected, ask = FALSE)
trying URL 'https://cloud.r-project.org/src/contrib/jsonlite_2.0.0.tar.gz'
Content type 'application/x-gzip' length 1055849 bytes (1.0 MB)
##### snipped #####
* DONE (jsonlite)
Source-package output includes compiler and installation checks. Windows and macOS may install a binary package instead when the repository provides one.
Related: Fix an R package 00LOCK error
> installed.packages(lib.loc = package_lib)[rownames(selected), "Version", drop = FALSE]
Version
jsonlite "2.0.0"
> remaining <- old.packages(lib.loc = package_lib, repos = getOption("repos"))
> intersect(rownames(selected), rownames(remaining)) NULL
> jsonlite::fromJSON('{"answer":42}')$answer
[1] 42