An R package can behave differently across operating systems and R releases because compiled dependencies, examples, vignettes, and tests all participate in R CMD check. GitHub Actions can run those checks for every pull request before a change reaches the package's main branch.

The maintained r-lib/actions components separate R setup, dependency installation, and package checking. The standard matrix covers release R on Linux, macOS, and Windows, then adds development and previous R releases on Linux so platform failures and version regressions appear as distinct jobs.

GitHub supplies each job with a repository-scoped GITHUB_TOKEN that can read the checked-out repository contents, so a personal token is unnecessary. The final API check reads the workflow run for the tested commit and its matrix jobs without changing the repository.

Steps to check an R package with GitHub Actions:

  1. Create the workflow directory.
    $ mkdir -p .github/workflows
  2. Start the workflow file with its header.
    R-CMD-check.yaml
    # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples
    # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help
    on:
      push:
        branches: [main, master]
      pull_request:
        branches: [main, master]
    
    name: R-CMD-check
    
    permissions:
      contents: read

    The pull_request event checks changes proposed for main or master, while the push event checks commits that reach those branches. The token can read repository contents for checkout; every unlisted permission is none.

  3. Add the runner matrix.
    R-CMD-check.yaml
    jobs:
      R-CMD-check:
        runs-on: ${{ matrix.config.os }}
    
        name: ${{ matrix.config.os }} (${{ matrix.config.r }})
    
        strategy:
          fail-fast: false
          matrix:
            config:
              - {os: macos-latest,   r: 'release'}
              - {os: windows-latest, r: 'release'}
              - {os: ubuntu-latest,   r: 'devel', http-user-agent: 'release'}
              - {os: ubuntu-latest,   r: 'release'}
              - {os: ubuntu-latest,   r: 'oldrel-1'}

    fail-fast: false lets every platform report its result even when another matrix job fails.

  4. Set the job environment.
    R-CMD-check.yaml
        env:
          GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
          R_KEEP_PKG_SOURCE: yes

    The GITHUB_PAT value comes from GitHub's per-job token. A personal token is unnecessary unless the package genuinely needs access beyond the current repository.

  5. Add the checkout step.
    R-CMD-check.yaml
        steps:
          - uses: actions/checkout@v4
  6. Add the Pandoc setup step.
    R-CMD-check.yaml
          - uses: r-lib/actions/setup-pandoc@v2
  7. Add the R setup step.
    R-CMD-check.yaml
          - uses: r-lib/actions/setup-r@v2
            with:
              r-version: ${{ matrix.config.r }}
              http-user-agent: ${{ matrix.config.http-user-agent }}
              use-public-rspm: true
  8. Add dependency installation.
    R-CMD-check.yaml
          - uses: r-lib/actions/setup-r-dependencies@v2
            with:
              extra-packages: any::rcmdcheck
              needs: check

    The dependency action reads the package's DESCRIPTION file and adds packages declared under Config/Needs/check.
    Related: How to install an R package from GitHub

  9. Add the package check.
    R-CMD-check.yaml
          - uses: r-lib/actions/check-r-package@v2
            with:
              upload-snapshots: true

    upload-snapshots preserves changed test snapshots when a job fails. A reviewed full commit SHA is the immutable alternative to a version tag when organizational policy requires pinned actions.

  10. Review the completed workflow.
    R-CMD-check.yaml
    # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples
    # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help
    on:
      push:
        branches: [main, master]
      pull_request:
        branches: [main, master]
    
    name: R-CMD-check
    
    permissions:
      contents: read
    
    jobs:
      R-CMD-check:
        runs-on: ${{ matrix.config.os }}
    
        name: ${{ matrix.config.os }} (${{ matrix.config.r }})
    
        strategy:
          fail-fast: false
          matrix:
            config:
              - {os: macos-latest,   r: 'release'}
              - {os: windows-latest, r: 'release'}
              - {os: ubuntu-latest,   r: 'devel', http-user-agent: 'release'}
              - {os: ubuntu-latest,   r: 'release'}
              - {os: ubuntu-latest,   r: 'oldrel-1'}
    
        env:
          GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
          R_KEEP_PKG_SOURCE: yes
    
        steps:
          - uses: actions/checkout@v4
    
          - uses: r-lib/actions/setup-pandoc@v2
    
          - uses: r-lib/actions/setup-r@v2
            with:
              r-version: ${{ matrix.config.r }}
              http-user-agent: ${{ matrix.config.http-user-agent }}
              use-public-rspm: true
    
          - uses: r-lib/actions/setup-r-dependencies@v2
            with:
              extra-packages: any::rcmdcheck
              needs: check
    
          - uses: r-lib/actions/check-r-package@v2
            with:
              upload-snapshots: true
  11. Stage the workflow file.
    $ git add .github/workflows/R-CMD-check.yaml
  12. Commit the workflow file.
    $ git commit -m "Add R package checks"
  13. Push the current branch.
    $ git push --set-upstream origin HEAD
  14. Open the pull request from the current branch.
    $ gh pr create --fill

    GitHub starts one R-CMD-check job for each matrix row. A warning or error produces a failed job; open that job's artifact to inspect the retained check directory.

  15. Set the repository owner used by the workflow-run API request.
    $ OWNER=your-github-user-or-organization
  16. Set the repository name used by the workflow-run API request.
    $ REPOSITORY=your-r-package-repository
  17. Set the commit identifier checked by the pull request.
    $ COMMIT=your-current-commit-sha
  18. Download the workflow run for the tested commit.
    $ curl -fsSL \
      "https://api.github.com/repos/${OWNER}/${REPOSITORY}/actions/workflows/R-CMD-check.yaml/runs?head_sha=${COMMIT}&per_page=1" \
      -o R-CMD-check-runs.json
  19. Inspect the newest workflow run status and identifier.
    $ jq -r '.workflow_runs[0] | [.id, .status, .conclusion, .head_branch] | @tsv' \
      R-CMD-check-runs.json

    The status value becomes completed after the run finishes. A successful run reports success under conclusion and supplies the run ID needed for the matrix-job request.

  20. Set the completed run identifier returned by the previous command.
    $ RUN_ID=your-completed-run-id
  21. Download the matrix jobs for the completed workflow run.
    $ curl -fsSL \
      "https://api.github.com/repos/${OWNER}/${REPOSITORY}/actions/runs/${RUN_ID}/jobs?per_page=100" \
      -o R-CMD-check-jobs.json
  22. Confirm that every matrix job reports a successful conclusion.
    $ jq -r '.jobs[] | [.name, .conclusion] | @tsv' R-CMD-check-jobs.json

    All five rows must report success. A different conclusion identifies the operating system and R release whose job log needs review.

  23. Remove the temporary workflow-run response files.
    $ rm R-CMD-check-runs.json R-CMD-check-jobs.json