| name | update-renv |
| description | Update the packages of the gallery's own renv library, in R/renv.lock. Use when R/check.R or R/capture.R fails on a package version, when a package the tooling needs is absent from the lockfile, or when the lockfile is old enough to be worth refreshing. Not for apps/*/manifest.json, which rsconnect writes in each application's own repository. |
Update the renv library
R/renv.lock records the packages that this repository's own tooling needs:
R/check.R, which tests apps.yml and packages.yml, and R/capture.R,
which takes the thumbnails. Nothing else reads it. No workflow restores it, so
a mistake here stops a person at their console and never a pull request.
This is not apps/*/manifest.json. Those files record what Connect Cloud
installs for one application, rsconnect::writeManifest() writes them in the
application's own repository, and renv has no part in them. If the task is a
package version for a deployed application, this is the wrong skill.
The paths are not the default ones
.Rprofile sets RENV_PATHS_RENV to R/renv and RENV_PATHS_LOCKFILE to
R/, so the lockfile is R/renv.lock and the library is R/renv/library.
Operate R from the root of the repository and let .Rprofile do this. Never
pass a lockfile path by hand: a command that names renv.lock writes a second
lockfile at the root, and nothing reads that one.
The trap: renv::snapshot() with no arguments
R/renv/settings.json sets snapshot.type to implicit, so renv derives the
lockfile from the code it finds in the project. It finds apps/ as well.
renv::dependencies() discovers 69 packages across this repository, and 62 of
them are reached only through apps/: duckdb, SummarizedExperiment,
igvShiny, shinyreact, and several that exist only in a Posit-internal
repository. A bare renv::snapshot() tries to record all 62 in the gallery's
lockfile.
Snapshot the packages that R/ actually uses:
renv::snapshot(packages = unique(renv::dependencies("R", quiet = TRUE)$Package))
That call gives renv the seven direct dependencies of R/ — chromote, fs,
here, jsonlite, renv, stringr, yaml — and renv adds their recursive
dependencies itself.
renv::status() reports the same confusion, as a long list of packages that
are "used" and not "recorded". Those lines are apps/, and they are not a
problem to fix.
Steps
-
Restore before anything else. In R, from the root:
renv::restore()
Until the library exists, renv::status() says only that nothing is
installed, and renv::update() has nothing to compare. A cold renv cache
makes this step a few minutes; a warm one makes it seconds, because renv
links from the cache instead of installing.
-
Move the date, then update. The lockfile names a dated Package Manager
snapshot, https://packagemanager.posit.co/cran/2026-09-04 or later. That
repository is frozen, so renv::update() against it reports that every
package is up to date, whatever CRAN has released since. Moving the date is
the update.
l <- renv::lockfile_read("R/renv.lock")
l <- renv::lockfile_modify(l, repos = c(CRAN = "https://packagemanager.posit.co/cran/2026-11-20"))
renv::lockfile_write(l, "R/renv.lock")
renv::update()
Use today's date, or an older one deliberately. lockfile_modify() is the
way to write it: options(repos =) followed by a snapshot looks equivalent
and is not, because renv resolves packages against the lockfile's
repositories rather than options(repos), and the snapshot then rewrites
each package's Repository field from the name CRAN to the literal old
URL. Restore survives that, but the records are false, and a URL there
becomes binding under .
Notes
A record's Repository field is the name that options("repos") carried when
that package was installed, which install.packages() writes into the installed
DESCRIPTION and renv copies out. It is not in the source tarball. So a record
saying RSPM is a fossil of another machine's configuration, and three of them
are in this lockfile. renv passes the field as prefer to
renv_available_packages_entry(), a tie-break between repositories that both
carry the package, so a name matching nothing costs nothing. Leave those
records alone; reinstalling a working package to correct a field that decides
nothing is not worth the diff.
The lockfile's R.Version field records whoever last ran snapshot(). Let
snapshot() write it and mention the change in the commit message if it moves.
Editing that field by hand records a claim that no library on any machine
supports.
R/renv/library is gitignored, along with the rest of R/renv except
activate.R and settings.json. Only the lockfile is a reviewable artifact.
The seven direct dependencies of R/ need 29 packages with their recursive
dependencies. A lockfile materially larger than that has a tail from an earlier
version of the tooling, and the scoped call in step 4 removes it. That happened
once already: a lockfile of 57 packages carried googledrive, readr, dplyr
and tidyr, which nothing in the repository names, and those four brought 24
more with them.