| name | cran-submission |
| description | Finalize R packages for CRAN submission. Use when the user wants to: submit a package to CRAN, prepare for CRAN release, run pre-submission checks, do reverse dependency checks (revdep), run cross-platform checks via rhub, verify pkgdown builds, review CRAN policies compliance, or says "/cran-submission". Covers the full workflow from pre-flight checks through submission and post-acceptance steps. Complements r-package-coding (development) with release-specific procedures.
|
CRAN Submission
End-to-end workflow for preparing and submitting an R package to CRAN.
Workflow Overview
- Pre-flight: Verify version, NEWS, cran-comments.md, git state
- Local checks:
R CMD check --as-cran with current R-devel
- Extra checks: CRAN-specific requirements
devtools::check() misses
- pkgdown: Verify site builds (if present)
- Cross-platform checks: rhub / win-builder
- Reverse dependency checks: revdepcheck (updates only)
- Update cran-comments.md: Summarize results
- Submit:
devtools::submit_cran()
- Post-acceptance: Tag release, bump dev version
Adapt based on context: first submission vs update, patch vs minor/major.
Parallelization Strategy
Most steps are independent and MUST run concurrently. After pre-flight
(Step 1), launch as many steps as possible in parallel:
Parallel group A (launch simultaneously after Step 1)
- Step 2 (local check): Run via background Agent (
Bash with
run_in_background: true): Rscript -e 'devtools::check(remote=TRUE, manual=TRUE)'
- Step 4 (pkgdown): Run via background Agent if pkgdown is present:
Rscript -e 'pkgdown::build_site()'
- Step 5 (rhub): Kick off
rhub::rhub_check() -- runs remotely on GitHub
Actions, results arrive asynchronously. Also devtools::check_win_devel().
- Step 6 (revdep): Run via background Agent (long-running):
Rscript -e 'revdepcheck::revdep_check(num_workers=4)'
Sequential / main thread
- Step 3 (extra checks): Do this yourself in the main thread while the
background checks run -- review DESCRIPTION, documentation, URLs, code
policies. This is the part that benefits from agent reasoning.
- Step 7 (cran-comments.md): Collect results from all parallel steps,
then write.
- Steps 8-9: Sequential, after everything passes.
Implementation pattern
# In a single message, launch all background work:
Agent(prompt="Run devtools::check(remote=TRUE, manual=TRUE) in R...",
run_in_background=true)
Agent(prompt="Run pkgdown::build_site() in R...",
run_in_background=true)
Agent(prompt="Run revdepcheck::revdep_check(num_workers=4) in R...",
run_in_background=true)
Bash(command="Rscript -e 'rhub::rhub_check(...)'",
run_in_background=true)
Bash(command="Rscript -e 'devtools::check_win_devel()'",
run_in_background=true)
# Then proceed with Step 3 (extra checks) in the main thread.
# When background tasks complete, collect results for Step 7.
Do NOT serialize these steps. The whole point is that devtools::check takes
minutes, revdep_check takes hours, and rhub/win-builder are remote -- use
that time for the manual review work in Step 3.
Step 1: Pre-Flight
desc::desc_get_version()
usethis::use_news_md()
usethis::use_cran_comments()
usethis::use_release_issue()
Check that:
NEWS.md documents all user-visible changes for this version
LICENSE year matches current year
.Rbuildignore is up to date
- No dev-only files leak into the tarball
Step 2: Local Checks
Run in background Agent -- do not wait; proceed to Step 3.
remote = TRUE triggers the "CRAN incoming feasibility" check, which fetches
<repos CRAN url>/src/contrib/PACKAGES.in. This machine's default .Rprofile
points repos["CRAN"] at a Posit Package Manager binary mirror for speed,
and PPM never hosts PACKAGES.in (any PPM URL form 404s on it) -- only real
CRAN mirrors have it. Point at the real mirror for this specific check,
in-line so it doesn't depend on .Rprofile still defining use_real_cran():
options(repos = c(CRAN = "https://cloud.r-project.org"))
devtools::check(remote = TRUE, manual = TRUE)
Must pass with 0 errors, 0 warnings, 0 notes (or notes explained in
cran-comments.md). Test against R-devel when possible.
Step 3: CRAN Extra Checks (main thread)
Do this in the main thread while background checks run. These are ad-hoc
requirements CRAN reviewers enforce but R CMD check does not catch. See references/cran-extrachecks.md
for the full checklist. Key areas:
DESCRIPTION
- Title: Title Case, < 65 chars, no "for R"/"A Toolkit for", software names
in single quotes. Use
tools::toTitleCase().
- Description: 3-4 sentences, never starts with "This package"/pkg name,
expand all acronyms, software names in single quotes, function names unquoted.
- Authors@R: Must include
[cph] role.
- License year: Must match submission year.
Documentation
- Every exported function needs
@return (even @keywords internal).
- Every exported function with meaningful return needs
@examples.
- No commented-out example code. No gratuitous
\dontrun{}.
- Guard examples needing suggested packages with
@examplesIf.
README
- If
README.Rmd exists, edit that (not .md) then devtools::build_readme().
- Include
install.packages("pkg") instructions.
- No relative links to
.Rbuildignored files.
URLs
urlchecker::url_check()
urlchecker::url_update()
All URLs must be HTTPS, no redirects. Leave aspirational CRAN badge URLs as-is.
Non-ASCII Characters
Critical: CRAN's R-devel pretest builds the PDF manual with LaTeX. Any
non-ASCII characters in roxygen comments (em-dashes, tensor product symbols,
approx-equal signs, smart quotes, etc.) cause fatal LaTeX errors. This passes
R CMD check locally but fails the CRAN pretest.
grep -rPn '[^\x00-\x7F]' R/ vignettes/
Replace common offenders: -- for em-dash, \eqn{\otimes} for tensor
product in Rd math, ~ or approx for approximately-equal, straight
quotes for smart quotes. Run this check before devtools::document()
so regenerated man pages are also clean. Vignette .Rnw sources are also
processed by LaTeX and fail on the same characters.
Code Policies
- No
T/F -- use TRUE/FALSE
- No
options(warn = -1) -- use suppressWarnings()
- No
installed.packages() -- use requireNamespace()
- Restore
par(), options(), setwd() via on.exit()
- Never write outside
tempdir() -- including clipboards, ~/, .Rprofile.
The one sanctioned exception is tools::R_user_dir(pkg, which=...) (R >= 4.0)
with explicit user opt-in, keeping contents small
- Do not modify
.GlobalEnv or set persistent env vars (Sys.setenv) that
leak outside the R session
- Do not launch external apps (browsers, PDF viewers) in examples/tests/
vignettes unless closed afterwards
- Max 2 cores in examples/tests/vignettes (includes BLAS threads, OpenMP,
data.table::setDTthreads())
- Handle network failures gracefully with informative
message() (not
stop()); use HTTPS; handle HTTP 429/403 without retry storms
- No binary executables in source; no
::: to other packages' internals;
no assert/abort/exit/STOP/q() to terminate the R process
Example timing
CRAN enforces per-example time budgets -- "examples should run for no
more than a few seconds each". R CMD check --as-cran NOTEs examples
exceeding ~5s elapsed, WARNs at ~10s+. Gate slow examples with
\donttest{} (still runs on CRAN) or @examplesIf (conditional).
\dontrun{} is a red flag and CRAN reviewers routinely demand its
removal -- use only when the code genuinely cannot run in a non-interactive
environment.
Step 4: pkgdown Verification
Run in background Agent. Skip if the package has no pkgdown site. Otherwise:
pkgdown::check_pkgdown()
pkgdown::build_site()
Common issues:
url in _pkgdown.yml must match URL in DESCRIPTION
- All exported functions must appear in a custom reference index
- All vignettes must appear in a custom articles index
- Examples that error will break the build
Step 5: Cross-Platform Checks (rhub)
Fire-and-forget -- these run remotely. Launch via background Bash, then
move on. Results arrive asynchronously (GitHub Actions / email).
R-hub v2 (GitHub Actions based)
rhub::rhub_setup()
rhub::rhub_doctor()
rhub::rhub_platforms()
rhub::rhub_check()
Recommended platforms for CRAN submission:
linux -- Ubuntu latest
macos-arm64 -- macOS ARM
windows -- Windows latest
nosuggests -- without Suggested packages (catches unguarded imports)
clang-asan -- address sanitizer (for compiled code)
valgrind -- memory checker (for compiled code)
Win-builder (alternative for Windows)
devtools::check_win_devel()
devtools::check_win_release()
Results arrive by email. These are the same machines CRAN uses.
R Consortium runners (no GitHub needed)
rhub::rc_new_token()
rhub::rc_submit()
Step 6: Reverse Dependency Checks
Run in background Agent (can take hours). Skip for first submissions.
Required for updates, especially if the API changed.
Scope (per CRAN policy): check reverse strong dependencies, reverse
suggests, AND the recursive strong dependencies of those — not just direct
dependents. revdepcheck::revdep_check() handles this by default.
usethis::use_revdep()
revdepcheck::revdep_check(num_workers = 4)
revdepcheck::revdep_summary()
revdepcheck::revdep_report()
Interpreting Results
| Flag | Meaning |
|---|
+ | No new failures |
- | New failures introduced |
i- | Installation newly fails |
t- | Newly times out |
i+/t+ | Pre-existing issue (not your fault) |
A package shown as E: ?/? with EMPTY failure logs was never actually
checked — usually the timeout = budget (or a wall-clock kill) cut the run
off before that package's turn; revdep/failures.md then contains blank
"Error before installation" blocks. It does NOT mean the package breaks.
Don't set tight timeout = values "to be safe" (default is 10 min per
check, which is fine); re-run just the missing package with
revdep_add(pkg = "...") + revdep_check() (resumes), or verify manually by
rcmdcheck::rcmdcheck() on the CRAN tarball with the dev package installed.
If Revdeps Break
- Determine if breakage is a false positive, pre-existing, or real
- For real breakage from intentional API changes:
- Contact affected maintainers at least 2 weeks before submission
(CRAN policy — "ideally more"); provide patches or PRs where possible
- Document everything in
cran-comments.md, including which packages are
affected and when their maintainers were informed (the CRAN submission
form explicitly asks for this)
Custom revdep additions
Implement an unexported release_extra_revdeps() in the package to include
additional packages beyond the auto-detected set.
Step 7: Update cran-comments.md
Barrier step -- wait for all background Agents and remote checks to
complete before writing. Collect and summarize all results here.
Template:
## R CMD check results
0 errors | 0 warnings | 0 notes
## Test environments
- local: [OS], R x.y.z
- GitHub Actions: ubuntu-latest (R-devel, release, oldrel-1),
macOS-latest (release), windows-latest (release)
- R-hub: [platforms tested]
- win-builder: R-devel
## Reverse dependencies
Checked X reverse dependencies. No new issues found.
(Or: Y packages showed new failures. Details in revdep/cran.md.
Affected maintainers were notified on YYYY-MM-DD.)
For resubmissions, add a section explaining how previous feedback was addressed.
Step 8: Submit
usethis::use_version("minor")
options(repos = c(CRAN = "https://cloud.r-project.org"))
devtools::check_built(manual = TRUE, remote = TRUE)
devtools::submit_cran()
Submission policy rules
- Frequency cap: CRAN's rule of thumb is updates "no more than every
1--2 months" for established packages. Batch fixes; do not submit small
patches weekly.
- Confirm via email link (mandatory) to the maintainer address.
- Check status: https://CRAN.R-project.org/incoming/
- Do not resubmit while a version is pending review.
- Version must still bump on resubmission, even after a rejected upload.
- Resubmit via the web form's "Optional comment" field, not a separate
email. Explain what changed since the previous submission.
- All CRAN correspondence:
CRAN-submissions@R-project.org (new
submissions) or CRAN@R-project.org (published packages). Plain-text
ASCII only, no HTML, no attached tarballs.
- Maintainer must be a single person with a usable, unfiltered email
(not a mailing list). If the maintainer address changes, send confirmation
from the previous address to
CRAN-submissions@R-project.org.
Step 9: Post-Acceptance
usethis::use_github_release()
usethis::use_dev_version(push = TRUE)
Also:
- Close the release milestone on GitHub
- Publish blog post / changelog announcement if applicable
Quick Reference
| Function | Purpose |
|---|
usethis::use_release_issue() | GitHub checklist issue |
usethis::use_version() | Bump version |
devtools::check(remote=TRUE, manual=TRUE) | Full local check |
devtools::submit_cran() | Submit to CRAN |
devtools::build_readme() | Re-render README |
urlchecker::url_check() | Find URL problems |
tools::toTitleCase() | Format DESCRIPTION Title |
pkgdown::check_pkgdown() | Validate pkgdown config |
rhub::rhub_check() | Cross-platform checks |
revdepcheck::revdep_check() | Reverse dep checks |
devtools::check_win_devel() | Win-builder check |