Extra CRAN Submission Checks
Overview
Provides automated checks for common Comprehensive R Archive Network (CRAN) submission issues that are not caught by standard 'R CMD check'. Consolidates ad-hoc requirements that CRAN reviewers enforce but standard checks do not surface, helping R package maintainers identify and fix issues before submission to reduce rejection rates. Covers code-pattern issues, DESCRIPTION-field formatting, documentation problems, and general package structure concerns.
Every check now carries a severity tier, so a clean bill of health means something
precise rather than merely quiet. Checks read your examples, vignettes and demos as
well as R/, which is where several of the most common rejections actually land.
New checks bring part of CRAN's incoming filter offline, covering the Date,
Encoding and Version fields, the structure of Authors@R, ORCID and ROR
identifiers, a detectCores() that can return NA, and a scan for a leaked
credential. Existing checks are sharper and quieter, every check is callable on its
own, findings can go straight to your build system, checktor runs from anywhere
inside a package tree, and a package can tune checktor through Config/checktor/*
fields in its own DESCRIPTION.
Breaking changes
- checktor needs R 4.5.0 or later, where 0.1.0 asked only for R 3.5.0. R 4.5.0 added
tools::check_package_urls(), which the newurl_livenesscheck uses. An older installation stays on 0.1.0.
Show all changes in 0.2.0Hide the rest of the changes in 0.2.0
- The individual checks are now
lab_*(), so the doctor orders a panel of labs.diagnose_tf_usage()islab_tf_usage(), and the name afterlab_is the check nametidy()reports andConfig/checktorrefers to, which several old names did not match. The five category functions such asdiagnose_code_issues()keep their names, since they run a panel rather than one test. The names released in 0.1.0 were renamed outright rather than deprecated, so replacediagnose_withlab_in any call todiagnose_tf_usage(),diagnose_seed_setting(),diagnose_print_cat_usage(),diagnose_roxygen_usage(),diagnose_value_tags(),diagnose_example_structure(),diagnose_missing_examples(),diagnose_suggested_in_examples(),diagnose_package_size(),diagnose_urls(),diagnose_news_file()ordiagnose_cran_comments_file().diagnose_readme_relative_links()is nowlab_readme_links(), after thereadme_linkscheck it runs. Check names themselves are unchanged, so code that picks rows oftidy()orissues()by check name keeps working. - Every check carries a severity tier, and
checktor()gained aseverityargument deciding which tiers the verdict is about. It defaults to policy and robustness. - A policy finding is a citable violation of CRAN Repository Policy or Writing R Extensions.
- A robustness finding is a real defect that CRAN will still accept, such as a
detectCores()that may returnNA. - An opinion finding is a convention with no authority behind it.
Every check still runs and every finding is still reported with its tier. The tier
only decides what counts against a clean bill of health, so zero issues now means
your package is submission ready and nothing here will crash a user. checkup()
follows the same default, so a missing NEWS.md no longer fails a build. On a
checktor() result, metadata$total_issues, metadata$failed_checks,
n_issues(), n_failed_checks() and is_healthy() now count those tiers alone,
while issues() and failed_checks() still list every finding.
- A check that does not run is reported as skipped rather than as passing, so a clean bill of health never includes a check that never happened.
tidy()gained askippedcolumn andsummary()askippedcount, the names are inmetadata$skipped_checks, and the printed result and everyhealth_report()format name the checks that sat out. Printing a check or a category shows a skipped check as skipped,summary()counts it only as skipped, and a skipped row intidy()is neverpassed, so!passed & !skippedpicks out the failures and thepassed,failedandskippedcounts ofsummary()add up tochecks.passed()asks only whether a check failed, so it isTRUEfor a skipped one (#15, thanks @TroyHernandez). - Every check is exported, so any check
checktor()runs is one you can call yourself. The checks of theDESCRIPTIONfields take(path, verbose, desc = NULL), as the code checks take(path, verbose, parsed = NULL), so either can reuse a parse you already have.descmay be whatread.dcf()returns, andissues()andtidy()gained aseveritycolumn. description_bare_rwas removed. It asked you to quote every bareRin theDescription, which is not a rule anyone enforces. Writing R Extensions asks for single quotes around other packages and external software without namingReither way, and both forms clear CRAN, solanguage_namesleaves a bareRand a quoted'R'alone alike and takes no position on which you prefer.- Three checks left the default run because no authority supports them, and each stays exported for anyone who wants it. The CRAN rule behind
title_starts_with_articleapplies to theDescriptionand requires the word "package" after the article, not to theTitle. Writing R Extensions treats single quotes as an inclusive list for non-English usage that a quoted function name fits, which is whatdescription_function_quotesruled out. And?personsays authors who are natural persons hold copyright by default and need nocphrole, which is whatcph_roleasked for (#17, thanks @eddelbuettel).
New checks
detect_cores_robustnesscatches adetectCores()result used without anNAguard. The help says it returns an integer, orNAwhen the answer is unknown, andNA - 1isNA, so the next comparison dies withmissing value where TRUE/FALSE needed. The fix isparallelly::availableCores().- A family of checks mirrors CRAN's incoming filter, so you see those findings offline against your own sources before you submit.
date_formatcatches aDatethat is not ISO 8601yyyy-mm-dd, is over a month old, or lies in the future.version_formatcatches aVersioncomponent with a leading zero or a suspiciously large one, while leaving a calendar-year version alone.encoding_utf8catches anEncodingother than exactlyUTF-8. CRAN's incoming check callslatin1andlatin2deprecated, and a lower-caseutf-8draws the same NOTE.identifier_formatvalidates the ORCID and ROR identifiers inAuthors@R, and is reported as skipped whenAuthors@Rcannot be read.description_filecatches aDESCRIPTIONthat R cannot read: a line that is neither a field nor an indented continuation, a blank line that splits the file in two, or a file R cannot open at all, such as a directory in its place or one without read permission.R CMD buildandR CMD INSTALLboth stop on such a file. For a malformed line checktor used to skip theDESCRIPTIONchecks with nothing counted, so the file could not count against a clean bill of health, and for a blank line it judged the package on the fields above it alone. It is now a policy finding, the checks that read the fields, registered ones included, are reported as skipped, andprescribe()shows how to fix it.description_fieldscatches aDESCRIPTIONfield R does not know, which CRAN's incoming check NOTEs. The usual one isRemotes, which CRAN ignores since it installs dependencies from CRAN and Bioconductor alone. A typo such asBugreports,ImportorSuggestis silently ignored by R, so the finding names the field that was meant.Config/fields,RoxygenNoteand the other forms R allows pass.description_placeholderscatches aTitle,Description,AuthororMaintainerstill holding the text ausethisorpackage.skeleton()template wrote, such as "What the Package Does (One Line, Title Case)", with the tests CRAN's incoming check uses.title_package_namecatches aTitlethat is just the package name or opens with it and a colon, as intoypkg: Fit Simple Models, which Writing R Extensions asks you not to do and CRAN's incoming check NOTEs. ATitlethat opens with a name that is an ordinary word, as in "Survival Analysis", is left alone, since CRAN accepts it routinely.license_file_unneededcatches+ file LICENSEon a standard license such asGPL-3,LGPL-3or Apache, which the CRAN Cookbook asks you to drop along with the file, since "these are part of R". MIT and BSD, whose templates need the file, are left alone. ALICENSEthat adds attribution requirements is the exception CRAN allows, andConfig/checktor/allowrecords it.hardcoded_credentialsscans string literals inR/for a leaked secret, knowing the tokens and keys used by providers such as GitHub, AWS, Google, OpenAI, Anthropic and Stripe, along with PEM private keys and JSON Web Tokens. A token published to CRAN is public and must be revoked, andR CMD checkdoes not look for these. Only string literals are examined, so the same text in a comment never matches. See?lab_hardcoded_credentialsfor the full list.spellingrunsutils::aspell()over theTitleandDescriptionto mirror CRAN's incoming spelling pass, with the same ignores (single-quoted names,fn()calls,<doi:...>targets) and British anden_statswords. It reads any.aspell/dictionary,inst/WORDLIST, orConfig/checktorvocabulary you already keep, and is reported as skipped when no spell-check backend is installed or the one found fails to run. Turn it off withoptions(checktor.spelling = FALSE). When it reports a word,prescribe()prints a ready-to-paste.aspell/snippet, sinceinst/WORDLISTalone does not clear CRAN's aspell NOTE but a.aspell/dictionary does.url_livenessfetches every URL in the DESCRIPTION,.Rdfiles and vignettes and reports the ones that return an error, a 404, or a redirect, which is whatR CMD check --as-crandoes throughtools::check_package_urls(). It runs when you are at the console and stays off in scripts, in continuous integration and underR CMD check, where a slow or unreachable network would make the result depend on the machine rather than the package. Setoptions(checktor.url_check = TRUE)orFALSEto decide for yourself. With no network reachable it comes back marked as a check that did not run, rather than calling every link broken or quietly reading as though every link resolved, while a single unreachable host among reachable ones still counts.urlsremains the offline half, catchinghttp://links and shorteners without leaving the room.- A family of checks reads the code outside
R/, where several of the most common rejections land. Every code check used to readR/alone, so an install in an example, a write to the working directory in a vignette, or anoptions()call ininst/demothat was never put back all went unseen. Each rule below is one a maintainer has received verbatim from CRAN. example_interactiveasks forif (interactive())where an interactive function is hidden in\dontrun{}, so a reader sees it is not for a script. It reports one in\donttest{}too, sinceR CMD check --as-cranruns that code, where a prompt errors and an app waits for input. It reads the parsed example, so a function named in a comment or a string is not a call, an appshinyApp()builds without printing is not a launch, and only a guard that encloses the call excuses it,@examplesIf interactive()included (#19, thanks @TanguyBarthelemy). A guard still counts when it is combined with&&, or with||against another guard, wrapped insuppressWarnings(), split by a comment or kept in a variable assigned before it on every path, andinteractive() && f()guardsf()as anifwould. An assignment on the right of&&or||, which may never run, is not such a path.example_installscatches installing a package from an example, a vignette or a demo.example_writescatches a write to anywhere buttempdir(), judged with the same destination logic theR/check uses. The write checks now share one list of what counts as a write, so they agree with each other, and it covers the readr, data.table, arrow, spreadsheet and JSON writers alongside the base ones.write_csv(),write_rds(),fwrite(),write_xlsx(),write_parquet()andggsave()are all seen now, inR/and in examples alike.example_statecatchesoptions(),par()or the working directory changed and never put back. A change is put back when its old value is captured and later handed to the setter of the same kind in the same file, as inold <- setwd(tempdir())thensetwd(old).par(no.readonly = TRUE)is a read, not a change, and anon.exit()outside a function or a call that runs code in a frame of its own (local(),with(),within(),eval(),evalq()) is no restore:R CMD checknever runs it, and knitr runs it straight away.example_internal_nscatches:::in an example or vignette.example_tf_usagecatchesTorFforTRUEorFALSEin an example, a vignette or a demo, judged by the same ruletf_usageapplies toR/. Tests are read only withtests = TRUE, since CRAN rarely reads them.example_unparseablecatches an\examples{}section that is not valid R,\dontrun{}included, and names the line the parser stopped on.R CMD checkwrites\dontrun{}code out as comments and never parses it, while reviewers run it and send back "Unexecutable code in man/...". It is a robustness finding rather than policy, since Writing R Extensions lets\dontrun{}hold text that is not R.
internal_ns covers the same rule in R/, where a ::: call reaches an object
another author is free to change in routine maintenance. unexported_example_ns
used to suggest adding ::: to an example, which is the change CRAN asks you to
undo, so it now says to export the object or keep the topic internal instead.
Every check in this family reads an example as R runs it. An Rd % comment is
dropped, code that shares a line with a \dontrun{} or \donttest{} block is
still read, a line of a hidden block that is not R, such as a <your key>
placeholder, is passed over without hiding the rest of the example, and code under
#ifdef is read for every platform. Sweave (.Rnw) vignettes are read alongside
R Markdown and Quarto, and the line a finding gives is its line in the .Rd file
or vignette.
rd_bibliographyreads the\bibcitet{},\bibcitep{}and\bibshow{}macros R 4.6.0 added to.Rdfiles and reports whatR CMD checkwould: a key no bibliography holds, which R drops from the page, a key cited but never listed, and aREFERENCESfile left at the top level. Keys are looked up as R does, in the package'sREFERENCES.rds,.Ror.bib, in R's own bibliography, and forpkg::keyin an installed dependency, without runningREFERENCES.R.rd_bibliography_files, at robustness tier, reports aREFERENCES.bibwithoutbibtexinSuggestsand a bibliography that is not installed because it is outsideinst/or excluded by.Rbuildignore.language_namescatches a bare programming-language or statistical-computing name in theTitleorDescriptionthat CRAN asks to see single-quoted, covering names likePython,Java,JavaScript,Rust,MATLAB,SASandStata. It is the language counterpart tosoftware_names, kept separate because a language name and a package name are different kinds of thing. Single-letter and common-word names are left out so ordinary prose stays quiet, and you can extend the list withConfig/checktor/language_names. Likesoftware_names, it judges each mention on its own and skips a quoted longer name such as'MATLAB Runtime', a link, a DOI, a function call and a double-quoted title.format_namesreports a format or markup name written without single quotes in theTitleorDescription:JSON,HTML,XML,CSS,YAML,TOML,Markdown,LaTeX,TeXandSQL, plusC++,FortranandTcl, which CRAN packages write either way. It runs only when you call it, at opinion tier. A census of CRAN in September 2026 found that packages accepted at new-package review in the previous 18 months wrote these names bare 62% of
The NEWS section is longer than shown here.
From NEWS.md in the release
Install
Health
- OK13 OK2026-08-05
- NOTE12 OK, 1 NOTE2026-08-01
- OK6 OK2026-07-03
Documentation
- Help pages
- 107 (2 internal)includes a package overview page
- Examples that run
- 99%104 run in a plain check · 1 never run
- Exported functions with examples
- 99%
- Documented parameters
- 99%
- Return-value docs
- 99%
- Help pages citing references
- 1%
Downloads
Repository
- PRs merged (all time)
- 75%
- Median days to close, 50 most recently updated closed issues
- 25
- Median days to close, 50 most recently updated closed PRs
- 0
Stars over time
Releases over time
Issues over time
Commits over time
10 development-tooling and community-health practices detected across 6 families in the upstream repository
Checks run against github.com/coatless-rpkg/checktor on 2026-10-21.
The R CMD check workflow runs on Linux, macOS and Windows, and on R-devel
Funding links: GitHub Sponsors, Patreon, Buy Me a Coffee, thanks.dev
GitHub Pages site last deployed 2026-10-15 · The repository has a site deploy workflow
pkgdown site last built 2026-10-15
Show all practicesHide 2 practices
Dependencies
Installing checktor adds 5 packages (3 with compiled code, 1.6 MB of source tarballs). Heaviest: rlang 766 KB, cli 629 KB, xml2 152 KB.
Code & Tests
People & History
Pick two releases to compare their code metrics.
- 0.2.0Latest2026-10-14 · current release · diff ↗ ·
removed 13 exports
diagnose_cran_comments_file, diagnose_example_structure, diagnose_missing_examples, diagnose_news_file, diagnose_package_size, diagnose_print_cat_usage, diagnose_readme_relative_links, diagnose_roxygen_usage, diagnose_seed_setting, diagnose_suggested_in_examples, diagnose_tf_usage, diagnose_urls, diagnose_value_tagsadded 84 exports
ci_report, collect_rd_text, extract_rd_section, find_package_root, lab_acronyms, lab_authors, lab_browser_calls, lab_citation_file, lab_code_exercised, lab_commented_examples, lab_core_usage, lab_cph_role, lab_cran_comments_file, lab_date_format, lab_description_fields, lab_description_file, lab_description_function_quotes, lab_description_length, lab_description_placeholders, lab_description_quoted_quotes, lab_description_starts_with, lab_detect_cores_robustness, lab_donttest_vs_dontrun, lab_encoding_utf8, lab_example_installs, lab_example_interactive, lab_example_internal_ns, lab_example_state, lab_example_structure, lab_example_tf_usage and 54 more - 0.1.02026-07-02
- RR 4.6.0 released · 2026-04-24
Package metadata
- First published
- 2026-07-02
- Total releases
- 2 / 1 yrs
- License
- AGPL (>= 3) OSI
- Minimum R
- ≥ 4.5.0
- Download size
- 471 KB
Cite
Cite this package
Run in R for the authors' preferred citation:
citation("checktor")This package has no citation file, so citation() returns this entry built from DESCRIPTION.
Cite the R Observatory
For a number measured here: a download total, a coverage figure, an archival date.
From data release v2026-10-25, which the citation names so these numbers can be found later. More on citing and the projects behind them.