Workflows
Every capability is a reusable workflow you call with a small stub. The table below is the full catalog; each workflow links to its reference page, with full input tables and a copy-paste example.
| Workflow | Purpose | Key inputs |
|---|---|---|
check-bibliography-dois.yml |
Validate book/article BibTeX entries have resolvable DOIs matching CrossRef metadata | exclude-keys, install-quarto, no-metadata-check |
check-non-standard-chars.yml |
Detect curly quotes and en/em dashes in .qmd and .R files |
python-version |
check-phi.yml |
Scan PRs (added lines only) for content that looks like PHI | detectors, paths-ignore, allowlist-file, fail |
check-secrets.yml |
Scan the repository’s git history for committed credentials with gitleaks | version, checksums-sha256, config, paths-ignore, allowlist-file, log-opts, fail |
check-links.yml |
lychee link check with bundled config, PR skip-label, and auto-issue on main |
lychee-config, lychee-args, fail-if-empty, create-issue-on-main, skip-label |
lint-yaml.yml |
yamllint over tracked YAML with a bundled config, plus a check that flags long run: script blocks as decomposition candidates |
python-version, config-file, paths-ignore, fail, max-script-lines, fail-on-long-scripts |
lint-markdown.yml |
markdownlint-cli2 over tracked Markdown with a bundled config, plus a check that flags long fenced code blocks as decomposition candidates | config-file, globs, paths-ignore, fail, max-code-block-lines, fail-on-long-code-blocks |
check-new-line-breaks.yml |
Advisory, diff-scoped check that flags newly-added Markdown lines packing more than one sentence/clause onto one source line | python-version, globs, paths-ignore, fail, clause-breaks, clause-min-length |
lint-qmd.yml |
markdownlint over the prose sections of tracked .qmd Quarto files (code chunks stripped, YAML front matter skipped natively) with a bundled default config; default 80-char line-length ceiling encourages semantic line breaks |
config-file, globs, paths-ignore, fail, max-line-length |
lint-changed-lines.yml |
lintr over only the lines a PR adds or modifies (not whole changed files), so lint rules can be adopted or tightened incrementally | path, install-quarto, extra-packages, install-package, fail |
check-news.yml |
Enforce a NEWS.md changelog entry on PRs |
changelog, no-changelog-label |
test-coverage.yml |
Measure R-package test coverage with covr and upload the Cobertura report to Codecov |
path, install-quarto, extra-packages, fail-ci-if-error, upload-test-results |
update-snapshots.yml |
Regenerate testthat snapshots, accept the new output, commit, and push; snapshot correctness is judged at PR review of the pushed commit | ref, pr-mode, julia, extra-packages, apt-packages, commit-message |
summary.yml |
AI summary comment on newly opened issues | – |
quarto-publish.yml |
Render a Quarto site and deploy it to GitHub Pages | path, setup-r, r-packages, use-renv, tinytex, output-dir, checkout-submodules, deploy |
report-failure.yml |
File an issue when a watched job fails, or comment on the issue already open for that failure | title, body, labels |
preview.yml |
Build half of the PR-preview family: render in the PR context and upload an artifact (read-only) | path, r-version, apt-packages, use-renv, install-package, setup-chrome, submodules, render-profile |
preview-deploy.yml |
Deploy half: publish the artifact to gh-pages and comment the preview link |
– |
check-equation-renders.yml |
Crawl the build artifact with a headless browser and fail on equations MathJax can’t render | fail |
cleanup-pr-previews.yml |
Housekeeping: delete preview directories for closed PRs | preview-dir, compact-history |
claude.yml |
Agent-mode Claude Code bot: responds to @claude, edits files, opens/updates PRs |
setup-r, install-quarto, use-renv, apt-packages, pip-packages, checkout-submodules, use-ai-config, plugin-marketplaces, plugins, reviewer, report-cost |
claude-code-review.yml |
Read-only Claude PR review (default stub is mention-triggered via workflow_dispatch; add pull_request for automatic reviews) |
pr-number, prompt-addendum, checkout-submodules, allowed-bots, track-progress, apt-packages, pip-packages, lab-manual, check-latex-macros, use-ai-config, plugin-marketplaces, plugins, report-cost, model |
gemini.yml |
Gemini CLI question-answering bot: replies to @gemini and @gemini-cli on issues and PRs; routes @gemini review to the reviewer. Read-only — no file edits, pushes, or PRs |
setup-r, install-quarto, apt-packages, pip-packages, checkout-submodules, prompt-addendum, gemini-model, review-workflow-file |
gemini-code-review.yml |
Read-only Gemini PR review (default stub is mention-triggered via workflow_dispatch; add pull_request for automatic reviews) |
pr-number, prompt-addendum, checkout-submodules, gemini-model |
ai-code-review.yml |
Multi-agent PR review: picks one configured agent at random and dispatches its review workflow, falling through when one can’t be dispatched | agents, pr-number, claude-review-workflow-file, gemini-review-workflow-file |
bump-submodule.yml |
Update a submodule to upstream HEAD and open a PR when the pointer moves | submodule-path, remote-branch, base-branch, pr-branch |
sync-shared-fragments.yml |
Vendor files from an upstream repo (pinned to a commit) and open a PR when they change | source-repo, source-ref, source-paths, dest-dir, manifest-path |
sync-upstream.yml |
Merge an upstream repo’s branch into a fork and open a PR when the merge brings changes | upstream-repo, upstream-branch, base-branch, pr-branch, fail-on-conflict |
request-dependabot-review.yml |
Request review from configured reviewers when a PR’s author matches a bot actor (Dependabot by default) | reviewers, bot-actor |
altdoc-multiversion-docs.yml |
Render an altdoc-based R package’s docs and deploy multiple versions side by side on gh-pages (/dev/, /latest-tag/, /vX.Y.Z/, plus PR previews and a root redirect) |
r-packages, needs, apt-packages, setup-julia, checkout-submodules, default-branch, quarto-config-path, docs-base-url, preview-branch, timeout-minutes, rewrite-pr-preview-links, rewrite-issue-links, dispatch-version, dispatch-release-tag, legacy-paths, version-dropdown-title-template, version-in-navbar-title |
bump-dev-version.yml |
Bump an R package’s DESCRIPTION dev-version counter after every merge to main, and open/auto-merge a PR to carry it in |
description-path, base-branch, pr-branch, auto-merge, dry-run |
version-check.yml |
Fail a PR whose DESCRIPTION Version: differs from the base branch’s |
description-path, no-version-increment-label, bump-branch |
Versioning
@v1 was frozen at the pre-2.0.0 snapshot when the breaking quarto-publish change cut @v2, so any capability pinned there has picked up no fixes since - including non-breaking ones, like cleanup-pr-previews’s compact-history input, which does not exist at @v1 at all. Pin preview.yml, preview-deploy.yml, cleanup-pr-previews.yml, and quarto-publish.yml to @v2; test-coverage.yml, check-equation-renders.yml, lint-yaml.yml, lint-markdown.yml, lint-qmd.yml, lint-changed-lines.yml, check-new-line-breaks.yml, and check-secrets.yml only ever shipped at @v2 (too new to exist at the frozen @v1 tag). quarto-publish.yml additionally has a genuine behavioral fork: @v1 stays frozen on the older GitHub Actions Pages-artifact deploy, while @v2 deploys to the gh-pages branch instead - required if the same repo also uses the PR-preview family, since Pages can only have one Source. check-bibliography-dois.yml, check-phi.yml, check-links.yml, check-non-standard-chars.yml, claude.yml, claude-code-review.yml, and update-snapshots.yml also pin @v2: each picked up a real fix since the freeze (a dependency-pin bump, a new input, or a security fix) that a consumer still on @v1 would miss (audited in gha#182). request-dependabot-review.yml only ever shipped at @v2 too (it postdates the freeze - see gha#252), as does sync-upstream.yml (added after the freeze - see gha#254), altdoc-multiversion-docs.yml (added after the freeze), and report-failure.yml (added after the freeze - see gha#325). gemini.yml, gemini-code-review.yml, and ai-code-review.yml likewise only ever shipped at @v2, having been added well after the freeze - see gha#357. bump-dev-version.yml and version-check.yml postdate the freeze too (added in gha#388); pin both to @v2. summary.yml, bump-submodule.yml, and sync-shared-fragments.yml were audited in the same pass and found unchanged since the freeze, so @v1 remains current for them. check-news.yml was initially grouped with them, but later gained the configurable no-changelog-label input at gha#143 - pin it to @v2 too. The stubs in examples/ carry the tag and permissions: block documented on each workflow’s own reference page; copy the specific stub you need as-is.
Quality checks
These run on pull requests (and often on main) to keep a repo’s content clean.
check-bibliography-dois.ymlconfirms BibTeX entries have DOIs that resolve and match CrossRef metadata.check-non-standard-chars.ymlflags curly quotes and en/em dashes in.qmdand.Rfiles, which can break Quarto and R rendering.check-links.ymlruns a lychee link check with a bundled config, a PR skip-label, and an auto-filed issue whenmainbreaks.check-news.ymlenforces aNEWS.mdchangelog entry on each PR.lint-yaml.ymlruns yamllint over tracked YAML with a bundled config, plus a check that flags longrun:script blocks as decomposition candidates.lint-markdown.ymlruns markdownlint-cli2 over tracked Markdown with a bundled config, plus a check that flags long fenced code blocks as decomposition candidates.lint-qmd.ymlruns markdownlint over the prose sections of tracked.qmdQuarto files; code chunks are stripped before linting and YAML front matter is skipped natively. A default 80-char line-length ceiling encourages semantic line breaks.lint-changed-lines.ymlruns lintr over only the lines a pull request adds or modifies (not whole changed files), so lint rules can be adopted or tightened incrementally: new and edited code must comply while untouched legacy code is left alone.check-phi.ymlscans for content that looks like protected health information; see below.check-secrets.ymlscans the repository’s git history for committed credentials; see below.test-coverage.ymlmeasures R-package test coverage withcovrand uploads the Cobertura report to Codecov; pass theCODECOV_TOKENsecret through the caller’ssecrets:block (optional for public repos, but recommended).
PHI scanning
check-phi is a heuristic tripwire, not a HIPAA compliance tool. It flags patterns that should almost never be committed: US Social Security numbers, medical record numbers, dates of birth, and PHI-suggestive column headers in delimited data files (.csv/.tsv/.psv), so a human reviews before the data merges. It is tuned for high precision, so it misses free-text PHI such as patient names.
- Diff-scoped on PRs. Only lines added by the PR are scanned;
pushscans the whole tracked tree. - Values are never printed. Findings report only
file:line:coland the detector name, never the matched text, because a leaked identifier in a CI log is still a leak. - Suppressing false positives. Add a
phi-allowcomment on the line, or list a regex in an allowlist file (defaults to.github/phi-allowlist.txt). Usefail: falseto downgrade findings to warnings.
The phone and email detectors exist but are off by default because they are noisy in source; enable them through the detectors input.
Secret scanning
check-secrets is check-phi’s counterpart for credentials: check-phi detects identifiers and has no notion of a password or a token, so a committed credential passes it cleanly.
- It scans history, not the diff. A secret committed and then removed in a later commit is still exposed, because the orphaned commit stays fetchable through the GitHub API until the repository is garbage-collected. So the caller checks out with
fetch-depth: 0, and a shallow clone is refused rather than reported clean on a partial scan. - Values are never printed, in the log or the run summary. Findings report only the rule,
file:line, and the commit. - It blocks by default, where the advisory prose checks warn.
- Its
paths-ignorepatterns are Go regexes matched unanchored, not globs, sodocssuppresses every path containing that substring; anchor with^when that matters.
It complements GitHub’s native secret scanning rather than replacing it – that is a repository setting evaluating pushes, not a pull-request check – and neither substitutes for rotating an exposed credential. See check-secrets.yml for the full reference.
Quarto sites
This family builds and ships Quarto sites, and it builds this very site.
quarto-publish.ymlrenders a Quarto project and deploys it to thegh-pagesbranch onmain. One-time setup: set Settings -> Pages -> Source to “Deploy from a branch”, branchgh-pages/(root).- The preview family publishes a rendered site for each open PR to a
pr-preview/pr-<n>/directory ongh-pages. It is four cooperating workflows:preview.yml(build) renders the site and uploads it plus PR metadata as an artifact. It runs read-only in the possibly-forked PR context, so it cannot write to the base repo.preview-deploy.yml(deploy) triggers on the build’sworkflow_runcompletion, downloads the artifact, and publishes it togh-pagesin the base-repo context, then comments the preview link.check-equation-renders.ymlalso triggers on the build’sworkflow_runcompletion, downloads the same artifact, and crawls it with a headless browser to catch equations MathJax can’t typeset – invisible in the Quarto/pandoc build log, since MathJax only runs client-side.cleanup-pr-previews.yml(housekeeping) runs on a schedule and removes preview directories for PRs that have closed.
The build/deploy split is a trust boundary: untrusted fork code only ever runs in the read-only build half, while the privileged gh-pages push happens in the deploy half against base-repo code. Do not collapse them into one job.
Two wiring requirements:
- Both the deploy stub’s and the equation-check stub’s
on: workflow_run: workflows:value must match the build stub’sname:. That string is howworkflow_runfinds the build. workflow_runandscheduletriggers only fire for the copy of the file on the default branch, so previews and cleanup do not take effect until the stubs merge tomain.
This repo dogfoods all five. The caller stubs live in .github/workflows/: website-publish.yml, website-preview.yml, website-preview-deploy.yml, website-check-equation-renders.yml, and website-preview-cleanup.yml. They render this pure-markdown site with the R machinery turned off (no renv, no R CMD INSTALL, no Chrome).
Claude bots
The two Claude workflows are a pair.
claude.ymlruns the agent-mode Claude Code bot: it responds to@claudementions, edits files, and opens or updates PRs.claude-code-review.ymlruns a read-only Claude review: a consolidated summary on dispatched runs by default; add apull_requesttrigger in the caller if you also want automatic reviews on PR updates.
An @claude review mention (or any commit Claude pushes) routes through claude.yml, which dispatches claude-code-review.yml via workflow_dispatch. Install both, and keep the review stub named claude-code-review.yml (or set claude.yml’s review-workflow-file input to match) so the dispatch resolves. If you also want the direct /review path, enable issue_comment in the review stub too.
gemini.yml and gemini-code-review.yml are the same pair for Gemini CLI, with the same division of labour: the agent answers @gemini and @gemini-cli mentions, and an @gemini review dispatches the reviewer rather than being answered in place. Keep the review stub named gemini-code-review.yml (or set gemini.yml’s review-workflow-file to match). Gemini needs a GEMINI_API_KEY secret; the two families are independent, so a repo can install either or both.
ai-code-review.yml sits above whichever review workflows a repo has installed: it picks one of the configured agents at random and dispatches that agent’s own review workflow. Its fallback covers an agent that can’t be dispatched — no API key secret configured, or its review workflow file missing or disabled — not one that dispatches and then fails mid-run, since gh workflow run returns as soon as the run is queued and reports no run id to wait on (gha#362).
Content sync
Three workflows keep a repo current with content that lives elsewhere, without hand-bumping. The first two are the two directions of sharing single-source-of-truth content between a pair of repos; the third tracks an upstream a fork was cut from.
bump-submodule.ymlis for the side that vendors the other repo as a git submodule. A scheduled run advances the submodule to its upstream HEAD and opens a PR when it moved.sync-shared-fragments.ymlis for the side that cannot add a submodule because the other repo already submodules it (a mutual submodule would recurse). It vendors a pinned copy of the named files into adest-dir, records the source repo and commit in a JSON manifest, and opens a PR when the copy changes.sync-upstream.ymlis for a fork that tracks the project it was cut from. A scheduled run merges the upstream branch into a fork-owned automation branch and opens a PR when the merge brings changes, so the fork’s own changes are preserved and upstream’s updates are reviewed before they land. On a clean merge the PR is mergeable; on a conflict it carries the conflict markers for manual resolution.
All three reuse the open-sync-pr composite, which commits staged changes to a reused automation branch and opens or updates one PR (a no-op when nothing changed). For bump-submodule.yml/sync-shared-fragments.yml, scope each side to the other repo’s shared content so the two auto-PRs do not ping-pong.