r-cmd-check.yml
Run R CMD check across an OS x R-version matrix, wrapping r-lib/actions setup-r, setup-r-dependencies, and check-r-package. A second caller job with hard: true runs a Depends/Imports/LinkingTo-only check that is gated to pull_request.
Two designs come from IndrajeetPatil/workflows R-CMD-check.yaml (MIT): the hard input, and cache: false on that job so a restored pak cache cannot silently contain Suggests. Do not copy that file’s concurrency.group of ${{ github.workflow }}-${{ github.head_ref }} with no github.ref fallback: head_ref is empty on push, so every push across every branch collapsed into one group. This reusable workflow sets no concurrency; the example stub keys on github.ref.
error-on defaults to '"note"' to match rpt’s bespoke workflow, not r-lib’s '"warning"'. The _R_CHECK_* knobs, the matrix, the timeout, and rpt’s Julia / Quarto / pandoc / rocker/verse steps are inputs rather than hard-coded. See Migrating from rpt before treating this as a drop-in for that file.
This capability is workflow-only (no root composite): the matrix, the two mutually exclusive jobs, and the optional Linux container are job-level, so splitting the steps into a composite would add a nested @v2 bootstrap without reuse value, the same shape as update-snapshots.yml.
Inputs
| Input | Type | Default | Description |
|---|---|---|---|
hard |
boolean | false |
Run the hard-dependencies-only job instead of the full matrix. Gated to pull_request. Call the workflow twice from the stub if you want both. |
extra-packages |
string | '' |
Extra r-lib/actions package specs appended after the check’s own extras (any::rcmdcheck on the full matrix; knitr, rcmdcheck, rmarkdown, curl, and testthat on the hard job). |
error-on |
string | '"note"' |
R expression for check-r-package error-on on the full matrix. Default matches rpt (NOTEs fail). The hard job omits this so r-lib’s '"warning"' default applies. |
cran-incoming-remote |
boolean | false |
Value for _R_CHECK_CRAN_INCOMING_REMOTE_ and _R_CHECK_CRAN_INCOMING_. r-lib’s check-r-package sets INCOMING false when unset, so REMOTE alone cannot turn incoming checks on. |
force-suggests |
boolean | false |
Value for _R_CHECK_FORCE_SUGGESTS_ on the full matrix. The hard job always sets this false. |
stop-on-invalid-numeric-version-inputs |
boolean | true |
Value for _R_CHECK_STOP_ON_INVALID_NUMERIC_VERSION_INPUTS_. |
path |
string | '.' |
Path to the R package root (the directory holding DESCRIPTION). |
config |
string | (r-lib 5-way JSON) | JSON array of {os, r, http-user-agent?} objects for the full matrix. Ignored when hard is true. |
setup-pandoc |
boolean | true |
Install Pandoc before the check. |
install-quarto |
boolean | false |
Install Quarto via quarto-dev/quarto-actions/setup before the check. On the full matrix, skipped on ubuntu-latest only when linux-container contains verse. Separate from r-lib setup-r-dependencies’ own install-quarto (default auto when the tree has .qmd files). |
setup-julia |
boolean | false |
Install Julia before the check (for JuliaCall packages). |
julia-version |
string | '1' |
Julia version to install. Only used when setup-julia is true. |
linux-container |
string | '' |
Container image for ubuntu-latest cells of the full matrix (e.g. rocker/verse:latest). Ignored on the hard job. Quarto install is skipped on ubuntu only when this image name contains verse. |
checkout-submodules |
boolean | false |
Initialize the caller’s git submodules before the check. |
timeout-minutes |
number | 90 |
Job timeout. rpt’s bespoke workflow used 50. |
build-args |
string | 'c("--no-manual","--compact-vignettes=gs+qpdf")' |
R expression forwarded to check-r-package build_args. |
upload-snapshots |
boolean | true |
Upload all testthat snapshots as an artifact (r-lib’s wording). Default uploads on success too. |
upload-results |
boolean | true |
Upload the check results directory as a workflow artifact. |
Secrets
| Secret | Required | Description |
|---|---|---|
SUBMODULES_TOKEN |
No | Token with read access to private submodules. Only needed when checkout-submodules is true. |
Permissions
Only contents: read (the default), so no permissions: block is needed.
Hard-dependencies check
The standard matrix installs Suggests, so it cannot see a package that works for the author and breaks for a user who did not install them. hard: true installs only Depends, Imports, and LinkingTo, then adds back knitr, rcmdcheck, rmarkdown, curl, and testthat so the check itself can run.
cache: false on that job is load-bearing, not a speed knob: a restored pak cache may already contain a Suggested package, which would make a green hard-deps run meaningless. It is hard-coded, not an input.
The hard job also never uses linux-container. rocker/verse ships a large set of R packages, which is the same silent-Suggests failure cache: false exists to prevent. It omits error-on so r-lib’s '"warning"' default applies: missing Suggests are NOTEs, and error-on: note would fail the job for the condition it exists to tolerate.
Migrating from rpt
rpt’s bespoke .github/workflows/R-CMD-check.yaml is not a drop-in for this workflow’s defaults. Each of its steps has an equivalent or an input; none is silently dropped. This table is the feature-by-feature diff CLAUDE.md asks for before calling a reusable capability drop-in. Do not migrate rpt until a follow-up actually passes these through.
| rpt step | This workflow |
|---|---|
5-way OS x R-version matrix, fail-fast: false, devel http-user-agent: release |
Default config JSON. |
container: rocker/verse:latest on ubuntu-latest |
linux-container: rocker/verse:latest on the full job. The hard job never uses it. |
timeout-minutes: 50 |
timeout-minutes: 50 (default here is 90). |
actions/checkout with submodules: recursive |
checkout-submodules: true. |
r-lib/actions/setup-pandoc |
setup-pandoc: true (the default). |
julia-actions/setup-julia@v3 |
setup-julia: true. |
quarto-dev/quarto-actions/setup@v2 when os != ubuntu-latest |
install-quarto: true. Skipped on ubuntu only when linux-container contains verse. |
setup-r with r-version, http-user-agent, use-public-rspm: true |
Always. |
extra-packages: any::rcmdcheck and local::. |
any::rcmdcheck is always installed; pass local::. in extra-packages if you still want it. |
error-on: '"note"' |
Default on the full matrix. The hard job omits error-on so r-lib’s '"warning"' default applies (missing-Suggests NOTEs must not fail that job). |
build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")' |
Default build-args. |
(unset) _R_CHECK_CRAN_INCOMING_REMOTE_, _R_CHECK_FORCE_SUGGESTS_, _R_CHECK_STOP_ON_INVALID_NUMERIC_VERSION_INPUTS_ |
Inputs. IndrajeetPatil/workflows currently sets only _R_CHECK_CRAN_INCOMING_REMOTE_: false on the full job (fetched 2026-08-26); it does not set the other two. This workflow’s cran-incoming-remote also sets _R_CHECK_CRAN_INCOMING_, because check-r-package forces INCOMING false when unset. force-suggests: false matches r-lib’s unset override (and therefore rpt, which also leaves it unset). stop-on-invalid-numeric-version-inputs: true restates R’s default (stop unless that env is false). |
upload-snapshots: true |
Default true. |
(unset) upload-results (r-lib default false) |
Default true. Pass upload-results: false to match rpt. |
| No hard-deps job | Add the R-CMD-check-hard caller job from the example stub. |
Example
# Copy to .github/workflows/r-cmd-check.yml in your repo.
name: R-CMD-check
on:
push:
branches:
- main
pull_request:
workflow_dispatch:
concurrency:
group: r-cmd-check-${{ github.ref }}
cancel-in-progress: true
jobs:
R-CMD-check:
uses: Morrison-Lab/gha/.github/workflows/r-cmd-check.yml@v2
# with:
# error-on: '"note"'
# cran-incoming-remote: false
# force-suggests: false # matches r-lib/check-r-package when unset
# setup-julia: true
# install-quarto: true
# linux-container: rocker/verse:latest
# checkout-submodules: true
# timeout-minutes: 50
R-CMD-check-hard:
uses: Morrison-Lab/gha/.github/workflows/r-cmd-check.yml@v2
with:
hard: trueSee the examples/ directory for the full caller stub.
A brand-new capability is not usable at @v2 until that tag slides past this merge; pin the example as-is and wait for the slide, or point a template repo at this branch to try it.