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-ai-tells.yml |
Scan narrative prose in Markdown and Quarto files for AI-generated tell density and rhetorical markers | paths, paths-ignore, base-ref, threshold, ignore-tells, fail |
check-bibliography-dois.yml |
Validate book/article BibTeX entries have resolvable DOIs matching CrossRef metadata | exclude-keys, install-quarto, no-metadata-check, preinstalled-toolchain |
check-formatting.yml |
Fail when any .R/.r file would be rewritten by Air, Posit’s R formatter (Rust; no R session). Check-only |
version, path |
check-code-similarity.yml |
Flag code highly similar to a caller-supplied corpus of prior submissions, using JPlag. Computed entirely on the runner — nothing is uploaded. Warns rather than fails by default, since shared skeleton code and common idioms raise similarity legitimately | corpus-path, language, threshold, fail, base-code-path |
check-duplicate-roxygen.yml |
Check for duplicate roxygen parameter documentation across R code files and recommend consolidation using @inheritParams and/or @inheritDotParams |
path, paths-ignore, extensions, min-desc-length, base-ref, fail, python-version |
check-junk-files.yml |
Fail when the repository tracks operating-system or editor detritus (.DS_Store, AppleDouble ._*, .Rhistory, .RData, Thumbs.db), naming the git rm --cached fix and the global-gitignore / usethis::git_vaccinate() fix that stops it recurring |
patterns, paths-ignore, fail |
check-math-definitions.yml |
Check for duplicate, divergent, or colliding mathematical definitions and theorems across Quarto/Markdown files and sibling repositories | path, paths, repos, paths-ignore, extensions, prefixes, check-titles, fail, python-version |
check-merge-drops.yml |
Report content a merge commit silently dropped: lines either side added since the merge base that appear nowhere in the merge’s tree, the signature of a conflict resolved by keeping one side of a whole file (warn-only by default) | python-version, globs, paths-ignore, min-length, similarity, fail |
check-non-standard-chars.yml |
Detect curly quotes, en/em dashes, and the multiplication sign in .qmd, .R, and .md files |
python-version, extensions |
check-one-function-per-file.yml |
Enforce the one-function-definition-per-file rule across repository code files (.R, .py, .sh, .js, .ts, .jl) |
path, paths-ignore, extensions, opt-out-comment, fail, 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-repo-hygiene.yml |
Standard repository hygiene check suite (junk files, secrets, non-standard characters, new line breaks, typos, YAML lint, workflow lint, Markdown lint) | fail, check-junk-files, check-secrets, check-non-standard-chars, check-new-line-breaks, check-typos, lint-yaml, lint-workflows, lint-markdown |
check-quarto-website.yml |
Standard check suite for Quarto website repositories: repository hygiene, AI tells, QMD prose linting, lychee link checking, links to missing Quarto pages, orphaned images, typed code output, informal definitions in prose, and bibliography DOIs | fail, check-junk-files, check-secrets, check-non-standard-chars, check-new-line-breaks, check-typos, check-ai-tells, lint-yaml, lint-workflows, lint-markdown, lint-qmd, check-links, check-quarto-links, check-orphaned-images, check-typed-output, check-informal-definitions, check-bibliography-dois |
check-quarto-book.yml |
Standard check suite for Quarto book repositories: repository hygiene, AI tells, QMD prose linting, link checking across chapters, links to missing Quarto pages, orphaned images, typed code output, informal definitions in prose, bibliography DOIs, and equation renders | fail, check-junk-files, check-secrets, check-non-standard-chars, check-new-line-breaks, check-typos, check-ai-tells, lint-yaml, lint-workflows, lint-markdown, lint-qmd, check-links, check-quarto-links, check-orphaned-images, check-typed-output, check-informal-definitions, check-bibliography-dois, check-equation-renders |
check-quarto-manuscript.yml |
Standard check suite for Quarto manuscript repositories: repository hygiene, AI tells, QMD prose linting, link checking across manuscript sections, links to missing Quarto pages, orphaned images, typed code output, informal definitions in prose, bibliography DOIs, and equation renders | fail, check-junk-files, check-secrets, check-non-standard-chars, check-new-line-breaks, check-typos, check-ai-tells, lint-yaml, lint-workflows, lint-markdown, lint-qmd, check-links, check-quarto-links, check-orphaned-images, check-typed-output, check-informal-definitions, check-bibliography-dois, check-equation-renders |
check-r-package.yml |
Standard check suite for R package repositories: repository hygiene, AI tells, duplicate roxygen docs, one function per file, and Air formatting | fail, check-junk-files, check-secrets, check-non-standard-chars, check-new-line-breaks, check-typos, check-ai-tells, lint-yaml, lint-workflows, lint-markdown, check-duplicate-roxygen, check-one-function-per-file, check-formatting |
check-python-package.yml |
Standard check suite for Python package repositories: repository hygiene, Ruff linting and formatting, and pytest execution | fail, python-version, check-junk-files, check-secrets, check-non-standard-chars, check-new-line-breaks, check-typos, lint-yaml, lint-workflows, lint-markdown, check-ruff, check-formatting, check-tests |
student-qmd.yml |
Write a self-contained student .qmd per assessment source whose answers sit in .sol divs (includes inlined; answer-key-only divs and comments removed), check each against its source with Pandoc, and render each alone in an empty directory |
sources, working-directory, output-dir, hidden-classes, answer-profiles, student-profile, drop-render-chunks, prune-macros, require-answers, check, render, render-to, index-title, index-back-href, checkout-submodules, artifact-name |
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, fail-on-any-5xx |
check-quarto-links.yml |
Fail on relative links from .qmd/.md/.Rmd sources to pages that do not exist, which Quarto only warns about; source level, no render |
globs, target-extensions, paths-ignore, fail |
check-source-overlap.yml |
Flag Markdown and Quarto paragraphs a change adds that copy a reference text held as PDFs in a private corpus repository (word 6-grams); warn-only by default | corpus-repo, corpus-files, base-ref, paths, paths-ignore, min-run, fail |
check-orphaned-images.yml |
Report tracked images that no source file references by file name; warn-only by default | paths, extensions, source-extensions, paths-ignore, added-only, fail |
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 checks for long fenced code blocks, list-item merge splices, and blank lines that split a table | config-file, globs, paths-ignore, fail, max-code-block-lines, fail-on-long-code-blocks, base-ref, fail-on-item-splices, fail-on-table-splits |
check-new-line-breaks.yml |
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 |
check-typed-output.yml |
Flag typed (“faked”) code output in .qmd files – a # -> / #> / # Output: comment standing in for a printed value inside a code fence, or a hand-written output block right after one – whole-tree or diff-scoped, warn-only by default |
python-version, patterns, globs, paths-ignore, fail, diff-scoped |
check-quarto-prose.yml |
Diff-scoped, blocking prose check over .qmd and .md files: a notes div that holds an example, remark or definition, and a phrase from a list of idioms, cliches and slang |
globs, paths-ignore, base-ref, idioms-file, allow-file, fail |
check-informal-definitions.yml |
Flag technical concepts defined in running prose rather than inside formal Quarto definition divs (::: {#def-...}) – whole-tree or diff-scoped |
globs, paths-ignore, fail, diff-scoped, base-ref |
check-literal-math-letters.yml |
Diff-scoped, warn-only check that flags literal Latin and Greek letters in math (a bare y, \theta) where a semantic macro from Morrison-Lab/macros belongs |
globs, paths-ignore, fail, whole-tree |
check-wikipedia-further-reading.yml |
Diff-scoped, blocking check that flags a Wikipedia citation or link in a Further reading list item (Wikipedia pages are citations only) | globs, paths-ignore, fail, whole-tree |
check-redundant-citations.yml |
Diff-scoped, warn-only check that flags a source cited twice, with the same locator, in one div or passage (cite a source once per div) | globs, paths-ignore, fail, whole-tree |
check-self-credits.yml |
Diff-scoped, warn-only check that flags a formal credit to the group’s own work (a citation of an own bibliography entry, or a credit phrase beside an own site or name) | config, globs, paths-ignore, fail, whole-tree |
check-pr-concerns.yml |
Diff-scoped, blocking check that fails a PR mixing content and infrastructure paths | content-paths, infra-paths, paths-ignore, exempt-label, fail |
cancel-superseded.yml |
Cancels queued and running runs of a PR started for an old head or before the PR closed | runs-on |
check-def-commentary.yml |
Diff-scoped, blocking check that flags commentary written inside a #def- div (“It is not”, “Note that”, “Unlike” and similar) when a PR adds the line; commentary goes in a #rem- div |
globs, paths-ignore, fail, whole-tree |
check-unexplained-contrasts.yml |
Diff-scoped, warn-only check that flags a contrast (“X, not Y”, “does not mean”, “rather than a”) in a #def-, #rem- or #note- div that cites no source; a kept contrast must say who holds the other reading |
globs, paths-ignore, fail, whole-tree |
check-unexplained-choices.yml |
Diff-scoped check that fails when a binning choice in code sits in a figure, example or section whose prose gives no reason for it | globs, paths-ignore, fail, whole-tree |
check-solution-citations.yml |
Diff-scoped, blocking check that flags a citation of a solution (@sol-) inside a proof or result div when a PR adds the line; a derivation cites a result div that follows the solution |
globs, paths-ignore, fail, whole-tree |
check-exercise-before-result.yml |
Diff-scoped, blocking check that flags a result div (#thm-, #lem-, #cor-, #prp-) added or edited without an exercise and its solution just before it |
globs, paths-ignore, fail, whole-tree |
check-caller-permissions.yml |
Whole-tree, blocking check that fails when a caller job lacks the permissions a Morrison-Lab/gha reusable workflow’s jobs request |
workflows-dir, fail |
check-exm-figures.yml |
Diff-scoped, blocking check that flags an example div (#exm-) with no figure, when a PR adds a line to it |
globs, paths-ignore, fail, whole-tree |
check-contour-3d.yml |
Diff-scoped, warn-only check that flags a contour plot with no 3D surface plot of the same function, when a PR adds a line to it | globs, paths-ignore, fail, whole-tree |
check-tree-lists.yml |
Diff-scoped, blocking check that flags a definition, example or other labelled div that mentions a tree, holds a list nested three or more levels deep and has no figure, when a PR adds a line to it | globs, paths-ignore, fail, whole-tree |
check-search-shortcut.yml |
Blocking check that flags a Quarto site whose search opens on Ctrl/Cmd-F or Ctrl/Cmd-S: a _quarto*.yml project that sets no keyboard-shortcut, or any keyboard-shortcut that lists more than "/" |
globs, paths-ignore, fail |
check-visualization-figures.yml |
Diff-scoped, blocking check that flags a div that asks for a picture (a visual word in its title, or a sketch or plot request in an exercise prompt) but has no figure (an exercise may have it in its solution), when a PR adds a line to it | globs, paths-ignore, fail, whole-tree |
check-self-contained-captions.yml |
Diff-scoped, blocking check that flags a figure or table caption whose first sentence only points to another div | globs, paths-ignore, fail, whole-tree |
check-code-messages.yml |
Diff-scoped, warn-only check that flags a geom_smooth() or stat_smooth() call missing method or formula, and a geom_histogram() or stat_bin() call with no bins, binwidth or breaks, when a PR adds a line to it; the action also scans a rendered site for cell-output-stderr blocks |
globs, fail, whole-tree |
check-axis-titles.yml |
Diff-scoped, blocking check that flags an axis title made only of a symbol or expression, when a PR adds it | globs, paths-ignore, fail, whole-tree |
check-one-div-per-subfile.yml |
Diff-scoped, warn-only check that flags a subfile holding more than one top-level div, when a PR adds a line to it (one div per subfile; a proof stays with its claim and a solution with its exercise) | globs, paths-ignore, fail, whole-tree |
check-heading-levels.yml |
Diff-scoped check that flags a heading that breaks its page’s outline when a PR adds the heading line, and fails by default: a skipped level, a page whose first heading is deeper than level 1, or a first heading that repeats the page title (a page’s title is not a numbered level, so its body starts at level 1). | globs, paths-ignore, fail, whole-tree |
check-heading-titles.yml |
Diff-scoped check that flags a clickbait heading when a PR adds it, and fails by default: a question, or a heading (or a phrase after a colon) starting with who, whom, whose, what, when, where, why, how or which. | globs, paths-ignore, fail, whole-tree |
check-self-reference.yml |
Diff-scoped check that flags self-referential prose when a PR adds it, and fails by default: a sentence that starts with “This” or “These”, a structural noun and a verb such as shows, establishes or covers. | globs, paths-ignore, fail, whole-tree |
check-page-length.yml |
Diff-scoped, warn-only check that flags a page a PR adds to when the page, with its includes expanded, has more than 600 math expressions or 5000 source lines. | globs, paths-ignore, max-math, max-lines, fail, whole-tree |
check-heading-siblings.yml |
Diff-scoped, warn-only check that flags a heading a PR adds to a group of more than 7 sibling headings (same level, same parent, includes expanded). | globs, paths-ignore, max, fail, whole-tree |
check-figure-placement.yml |
Diff-scoped, warn-only check that flags a figure a PR adds when no prose cites it with @fig-id or when it sits directly after an example or exercise div instead of inside it. |
globs, paths-ignore, fail, whole-tree |
check-div-ids.yml |
Diff-scoped, warn-only check that flags a fenced div with a theorem-type class but no matching id, which would render as a plain block quote | globs, paths-ignore, fail, whole-tree |
check-multipart-exercises.yml |
Diff-scoped check that fails when a PR adds or revises an exercise div (#exr-) holding more than one part |
globs, paths-ignore, fail, whole-tree |
check-split-solutions.yml |
Diff-scoped check that fails when a PR touches a split exercise whose solution is not split with it | globs, paths-ignore, fail, whole-tree |
check-restated-results.yml |
Diff-scoped, warn-only heuristic check that flags a derivation solution (#sol- whose exercise asks to derive, prove, show or solve and whose solution displays an equation) when a PR edits it and no #thm-, #lem-, #cor- or #prp- div restates the result after the exercise |
globs, paths-ignore, fail, whole-tree |
check-extracted-lemmas.yml |
Diff-scoped, warn-only heuristic check that flags a solution, example or proof that states a general fact (“for any”, “for every” or “for all” with math) when a PR edits it and it cites no #thm-, #lem-, #cor- or #prp- result |
globs, paths-ignore, fail, whole-tree |
check-definition-equations.yml |
Diff-scoped, warn-only heuristic check that flags a definition div (#def-) that a PR edits and that shows more than the equation linking the defined symbol to its definitional expression: a chain of equalities, several display equations, or prose stating an equivalence; equalities and equivalences belong in a result |
globs, paths-ignore, fail, whole-tree |
check-hybrid-sentences.yml |
Diff-scoped, warn-only heuristic check that flags a sentence in a paragraph a PR edits that states one condition in words and another as a relation between math expressions (a hybrid sentence); write one sentence in words and one displayed in math | globs, paths-ignore, fail, whole-tree |
check-format-output-files.yml |
Fails when a Quarto page lacks an output-file for a format that a per-format publish renders |
formats, globs, paths-ignore, fail |
check-rubric-checklists.yml |
Diff-scoped check that warns when a PR adds or revises a grading rubric (Grading, Rubric, Marking scheme, Mark scheme) with no task-list item (- [ ] ...) |
globs, paths-ignore, fail, whole-tree |
check-single-ask-parts.yml |
Diff-scoped, warn-only check that flags a part of an exercise div (#exr-), meaning a list item or paragraph outside its solution, that asks for more than one thing; each part asks for one thing |
globs, paths-ignore, python-version, fail, whole-tree |
check-def-title-synonyms.yml |
Diff-scoped, warn-only check that flags a #def- div title listing synonyms with parentheses or “also called” instead of a comma list, primary term first |
globs, fail, whole-tree |
check-bare-yes-no.yml |
Diff-scoped check that fails when a PR adds a solution paragraph opening with a bare “Yes.” or “No.” instead of saying what the answer means in the question’s terms | globs, fail, whole-tree |
check-serial-comma.yml |
Diff-scoped check that fails when a PR adds a list of three or more items with no comma before the final “and” or “or” | globs, paths-ignore, fail, whole-tree |
check-exercise-background.yml |
Diff-scoped, warn-only heuristic check that flags an exercise whose title names an object that its body never mentions, when a PR adds a line of the exercise; a body gives the background its title implies | globs, fail, whole-tree |
check-exercise-question-paragraph.yml |
Diff-scoped check that fails when a PR adds a paragraph of an exercise that mixes scenario information with the instruction or question; the task goes in its own paragraph | globs, fail, whole-tree |
check-key-equations-displayed.yml |
Diff-scoped heuristic check that fails when a PR adds an inline equation in a proof that restates a displayed equation of the theorem or ends a concluding sentence; a key equation goes in display math | globs, fail, whole-tree |
check-pr-size.yml |
Diff-scoped, blocking check that fails a PR changing more than 10 files or 300 lines; a sweep goes out as minimal batches | max-files, max-lines, paths-ignore, exempt-label, fail |
check-note-sheet-rows.yml |
Diff-scoped check, warn-only for the one-concept rules and always blocking for prose and non-table lists, that flags a note-sheet table row holding more than one concept (notation in a label, a label joining two concepts, two formulas in one cell), when a PR adds it | globs, paths-ignore, fail, whole-tree, runs-on |
check-definite-antecedents.yml |
Diff-scoped, warn-only heuristic that flags a div whose first sentence opens with an unlinked The/These/Those/This/That, so no antecedent precedes it in the div (psw#291) | globs, paths-ignore, fail, whole-tree |
check-local-macros.yml |
Diff-scoped, warn-only check that flags LaTeX macros defined outside Morrison-Lab/macros (\newcommand, \def, \DeclareMathOperator), allowing a macros-override.* file to redefine only macros the library already has |
globs, paths-ignore, override-files, library, submodules, fail, whole-tree |
check-paired-file-change.yml |
Fail a PR that changes a file matching trigger-paths without also changing a file matching required-paths, unless the PR carries the skip label (read live from the API) |
trigger-paths, required-paths, skip-label, message |
check-term-links.yml |
Diff-scoped, blocking check that flags a defined technical term whose first use in a div or paragraph is not linked to its definition, and a bold term no definition div covers; the terms come from the corpus’s own #def- divs and links |
globs, paths-ignore, terms-file, allow-file, fail, whole-tree |
annotation-gate.yml |
Fail the run when any other job carries a notice, warning or failure annotation not on an allowlist, so an annotation cannot sit on a green job | levels, allow-patterns, job-name |
annotation-gate-run.yml |
Gate a finished run from a workflow_run workflow and post the result as an annotation-gate check run, for repositories whose checks are spread over many workflow files |
run-id, head-sha, run-attempt, workflow-id, all-runs, defer-while-running, levels, allow-patterns |
check-fform-paren.yml |
Diff-scoped, warn-only check that flags \name\paren{...} (or \sb{, \cb{) where an f form \namef{...} is defined in the macros file (default latex-macros/macros.qmd) |
macros-file, globs, paths-ignore, submodules, fail, whole-tree |
check-macro-expansions.yml |
Diff-scoped, warn-only check that flags math that writes out by hand what a shared macro already says, such as \sum_{i=1}^n where \sumin exists, using the expansions in the macros file (default latex-macros/macros.qmd) |
macros-file, exclude, globs, paths-ignore, submodules, fail, whole-tree |
check-dependency-graph.yml |
Run the self-test of a notes repo’s vendored build_dependency_graph.py, then build its definition and result graph and fail on a cycle |
script-dir, pages-dir, site, fail-on-cycle |
check-write-idiom.yml |
Diff-scoped, warn-only check that flags a line opening with Write the, Write a or Write an (after an optional list marker and label), because “Write …” hides whether to derive, express, state or give |
globs, paths-ignore, fail, whole-tree |
check-avoided-words.yml |
Diff-scoped check (warn-only, except that a trailing parenthetical link such as (@def-x) after a word always fails) that flags “above”/“below” for a place in the document, “just”/“easy”/“easily”, time-relative words such as “currently” and “recently”, a generic placeholder set such as “domain A”, and back-references such as “the setup of Exercise 1” that name nothing they reuse, per the Google developer documentation style guide |
globs, paths-ignore, fail, whole-tree |
check-proof-statements.yml |
Diff-scoped, warn-only check that flags a proof hidden from the slides (content-hidden for revealjs) and reasoning words such as “so” and “because” in a theorem statement |
globs, paths-ignore, fail, whole-tree |
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, use-renv, renv-cache-version, apt-packages, extra-packages, install-package, fail, file-pattern, lms-ref |
lint-changed-files.yml |
lintr over a PR’s changed files, a whole package, or a whole project, selected by scope |
scope, path, linter-file, install-quarto, use-renv, renv-cache-version, apt-packages, extra-packages, install-package, fail, preinstalled-toolchain |
lint-workflows.yml |
actionlint (syntax/semantics) and zizmor (security) over the caller’s GitHub Actions workflows and composite actions | path, actionlint-version, actionlint-checksum, zizmor-version, python-version, pedantic, fail |
spellcheck.yml |
Spellcheck an R package’s prose – DESCRIPTION’s Title/Description, man/*.Rd, vignette sources, and root README/NEWS/CHANGES/index Markdown – with {spelling}, accepting the package’s own inst/WORDLIST |
path, exclude, fail, additional-options, install-quarto, preinstalled-toolchain |
check-typos.yml |
Diff-scoped spellcheck of the files spellcheck.yml cannot see – Quarto site pages, CONTRIBUTING.md-class Markdown, YAML, code comments, and non-R-package repos – with crate-ci/typos |
version, checksums-sha256, path, config, globs, paths-ignore, base-ref, 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, examples-coverage, min-coverage |
check-extra.yml |
Extra R-package checks that R CMD check passes over: warnings as errors on examples/tests/vignettes, random test order, and a README.Rmd render that can also fail when README.md is stale |
path, extra-packages, install-quarto, check-warnings, check-random-order, check-readme, check-readme-freshness |
r-cmd-check.yml |
Run R CMD check across an OS x R-version matrix, with an optional hard-dependencies-only job gated to pull_request |
hard, error-on, force-suggests, setup-julia, julia-project, apt-packages, brew-packages, brew-casks, install-quarto, linux-container, extra-packages, timeout-minutes |
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, preinstalled-toolchain, commit-message; secret WORKFLOW_TOKEN |
summary.yml |
Claude summary comment on newly opened issues (needs CLAUDE_CODE_OAUTH_TOKEN) |
model |
quarto-publish.yml |
Render a Quarto site and deploy it to GitHub Pages | runs-on, path, setup-r, r-packages, use-renv, install-package, setup-chrome, tinytex, setup-julia, julia-version, preinstalled-toolchain, apt-packages, output-dir, render-profile, formats, per-file-formats, freeze-cache, freeze-cache-save-on-failure, deno-v8-options, checkout-submodules, pre-render-artifact, pre-render-artifact-path, fail-on-render-warning, forbid-log-patterns, timezone, timeout-minutes, 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) | runs-on, path, r-version, r-packages, apt-packages, use-renv, install-package, setup-chrome, tinytex, setup-julia, julia-version, preinstalled-toolchain, submodules, render-profile, output-dir, formats, skip-docx, skip-formats, extra-preview-labels, fail-on-render-warning, forbid-log-patterns, timezone, timeout-minutes, detect-changed-chapters, changed-chapters-banner, highlight-changes, changed-chapters-glob, deployed-branch, deployed-subdir, changed-chapters-normalize-patterns, max-elements-for-pairwise, banner-index, docx-tracked-changes, docx-tracked-changes-glob |
preview-deploy.yml |
Deploy half: publish the artifact to gh-pages and comment the preview link |
runs-on, pages-base-url, pages-base-path |
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, publish-workflow, wait-for-publish |
claude.yml |
Agent-mode Claude Code bot: responds to @claude, edits files, opens/updates PRs. A quoted or code-span mention starts only a cheap filter job, not the agent. |
setup-r, install-quarto, use-renv, apt-packages, pip-packages, checkout-submodules, use-ai-config, use-posit-skills, plugin-marketplaces, plugins, reviewer, dispatch-review-on-agent-push, report-cost, trusted-bot-logins, dispatch-on-assignee, reference-repos, reference-repos-token, extra-secret-names, timeout-minutes |
claude-code-review.yml |
Read-only Claude PR review (default stub is mention-triggered via workflow_dispatch; add pull_request for automatic reviews) |
runs-on, gate-runs-on, pr-number, prompt-addendum, checkout-submodules, submodule-paths, allowed-bots, track-progress, apt-packages, pip-packages, lab-manual, check-latex-macros, use-ai-config, use-posit-skills, plugin-marketplaces, plugins, reference-repos, reference-repos-token, report-cost, model, extra-secret-names, timeout-minutes |
claude-manage-project.yml |
Triage a newly-opened issue: apply a priority label and add it to the project board. Gated to OWNER/MEMBER/COLLABORATOR authors, since the issue body reaches a write-capable agent | prompt-addendum, trusted-bot-logins |
opposition-research.yml |
Automated opposition research: mine competitor community demand for features and file high-value opportunities as issues | competitor, target-repo, scope-guidance, dry-run, max-issues, labels, model, use-ai-config, timeout-minutes |
check-dependency-updates.yml |
Automated dependency freshness audit: mine release notes for high-value updates and file discovered opportunities as issues | target-repo, dry-run, max-issues, labels, model, use-ai-config, timeout-minutes |
gemini.yml |
Agent-mode Gemini CLI bot: responds to @gemini and @gemini-cli, edits files, opens/updates PRs |
setup-r, install-quarto, use-renv, renv-cache-version, r-extra-packages, apt-packages, pip-packages, checkout-submodules, eager-pr, reviewer, mark-ready-for-review, prompt-addendum, gemini-model, review-workflow-file, extra-secret-names |
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, extra-secret-names |
antigravity-code-review.yml |
Automated agentic code review, security audit, or test-suite generation via Google Antigravity SDK (google-antigravity) |
mode, pr-number, prompt-addendum, trigger-policy, checkout-submodules, model, workload-identity-provider, service-account, gcp-project, gcp-location, max-diff-lines, max-diff-files, fail-on-error |
cursor-code-review.yml |
Queue a Cursor Bugbot PR review via the Enterprise Bugbot API; success means the review was queued | pr-number, dry-run |
opencode-code-review.yml |
Read-only OpenCode PR review running the opencode CLI headless (default stub is dispatch-triggered; add pull_request for automatic reviews) |
pr-number, prompt-addendum, checkout-submodules, opencode-model, opencode-version, opencode-attempts |
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 or fails during execution | agents, pr-number, claude-review-workflow-file, gemini-review-workflow-file, antigravity-review-workflow-file, cursor-review-workflow-file, opencode-review-workflow-file, watch-timeout |
small-model-agent.yml |
Small or self-hosted model PR agent: runs a small-model agent against a PR diff with bounded verification gates (wai#39, ai-config#1292) |
pr-number, endpoint-url, model, max-iterations, setup-r, install-quarto, apt-packages, pip-packages, checkout-submodules, run-gates, dry-run, extra-secret-names |
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 |
update-quarto-extensions.yml |
Update vendored Quarto extensions in-place to upstream semver tags and open a PR when newer versions are available | extension-repos, extensions-dir, base-branch, pr-branch, labels, dry-run, allow-local-edits |
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, root-landing-target, 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, preinstalled-toolchain |
Choosing the runner. Every check-* and lint-* workflow that takes no secrets, plus check-secrets.yml, cleanup-pr-previews.yml and annotation-gate-run.yml, accepts a runs-on input (default empty: the caller’s CI_RUNNER variable if set, else ubuntu-latest), so a private repository that sets CI_RUNNER to its self-hosted label runs every gha job there while GitHub-hosted minutes are unavailable. gha ignores CI_RUNNER for a public repository’s events (it reads github.event.repository.private), but leave it unset at the repository and the organization level in every public repository anyway. Never point a public repository at a self-hosted runner, because a pull request from a fork can run arbitrary code on it (#1167).
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, 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, lint-changed-files.yml, check-new-line-breaks.yml, check-secrets.yml, check-junk-files.yml, lint-workflows.yml, spellcheck.yml, check-typos.yml, check-extra.yml, check-formatting.yml, claude-manage-project.yml, r-cmd-check.yml, check-code-similarity.yml, and check-one-function-per-file.yml only ever shipped at @v2 (too new to exist at the frozen @v1 tag). check-dependency-updates.yml, check-duplicate-roxygen.yml, check-math-definitions.yml, check-merge-drops.yml, check-orphaned-images.yml, check-python-package.yml, check-quarto-book.yml, check-quarto-links.yml, check-source-overlap.yml, check-quarto-manuscript.yml, check-quarto-prose.yml, check-quarto-website.yml, check-r-package.yml, check-repo-hygiene.yml, check-typed-output.yml, check-informal-definitions.yml, check-literal-math-letters.yml, check-wikipedia-further-reading.yml, check-redundant-citations.yml, check-self-credits.yml, check-one-div-per-subfile.yml, check-heading-levels.yml, check-heading-titles.yml, check-self-reference.yml, check-div-ids.yml, check-page-length.yml, check-heading-siblings.yml, check-figure-placement.yml, check-code-messages.yml, check-multipart-exercises.yml, check-rubric-checklists.yml, check-single-ask-parts.yml, check-split-solutions.yml, check-restated-results.yml, check-extracted-lemmas.yml, check-definition-equations.yml, check-hybrid-sentences.yml, check-format-output-files.yml, check-caller-permissions.yml, check-exm-figures.yml, check-contour-3d.yml, check-visualization-figures.yml, check-axis-titles.yml, check-tree-lists.yml, check-search-shortcut.yml, check-self-contained-captions.yml, check-def-commentary.yml, check-unexplained-contrasts.yml, check-unexplained-choices.yml, check-solution-citations.yml, check-exercise-before-result.yml, check-pr-concerns.yml, cancel-superseded.yml, check-pr-size.yml, check-def-title-synonyms.yml, check-bare-yes-no.yml, check-serial-comma.yml, check-exercise-background.yml, check-exercise-question-paragraph.yml, check-key-equations-displayed.yml, check-note-sheet-rows.yml, check-definite-antecedents.yml, check-term-links.yml, check-paired-file-change.yml, annotation-gate.yml, annotation-gate-run.yml, check-local-macros.yml, check-fform-paren.yml, check-macro-expansions.yml, check-write-idiom.yml, check-avoided-words.yml, check-proof-statements.yml, check-dependency-graph.yml, cleanup-pr-previews.yml, opposition-research.yml, student-qmd.yml, summary.yml, update-quarto-extensions.yml, and update-snapshots.yml only ever shipped at @v3 (or moved to @v3 for cleanup-pr-previews.yml, summary.yml, and update-snapshots.yml). 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, and claude-code-review.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, antigravity-code-review.yml, cursor-code-review.yml, opencode-code-review.yml, and ai-code-review.yml likewise only ever shipped at @v2, having been added well after the freeze - see gha#357, gha#510, and gha#586. bump-dev-version.yml and version-check.yml postdate the freeze too (added in gha#388); pin both to @v2. small-model-agent.yml postdates the freeze too (added in gha#436); pin to @v2. check-ai-tells.yml postdates the freeze too (added in gha#382); pin to @v2. 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-ai-tells.ymlscans narrative prose in Markdown and Quarto files for AI tell patterns and density.check-bibliography-dois.ymlconfirms BibTeX entries have DOIs that resolve and match CrossRef metadata.check-junk-files.ymlfails when the repository tracks operating-system or editor detritus – a.DS_Store, an AppleDouble._*sidecar, an.Rhistoryor.RData. It reports each file, thegit rm --cachedline that clears it, and the per-machine fix that stops it recurring in every other repository too.check-math-definitions.ymlscans Quarto and Markdown sources for duplicate, divergent, or colliding math definitions and theorems across files and sibling repositories.check-non-standard-chars.ymlflags curly quotes, en/em dashes, and the multiplication sign in.qmd,.R, and.mdfiles, which can break Quarto and R rendering. The multiplication sign’s ASCII replacement depends on context:xor*incode, and
$\times$or×in prose, since Pandoc renders a\uXXXXescape literally rather than decoding it.check-links.ymlruns a lychee link check with a bundled config, a PR skip-label, and an auto-filed issue whenmainbreaks.check-quarto-links.ymlfails on a relative link from a.qmd,.md, or.Rmdsource to a page that does not exist. Quarto only warns about such a link (Unable to resolve link target), and lychee reads a.qmdas plain text, socheck-links.ymlnever sees it. It reads the sources, so no render is needed, and it resolves a link in an included subfile from the page that includes it, as Quarto does.check-quarto-prose.ymlblocks a pull request that adds an idiom, cliche or slang phrase to a.qmdor.mdfile, or that puts an example, remark or definition in a notes div. It reports only the lines a diff adds.check-orphaned-images.ymlreports tracked images that no tracked source file names, warning by default.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.file-patternextends the check to Quarto and R Markdown documents, linting their R chunks, andlms-refinstalls the sharedlmspackage and lints withlms::default_linters(nested_calls = TRUE), the lab’s linters including the no-nested-calls rules.lint-changed-files.ymlruns lintr at one of three scopes selected byscope: the files a pull request changed (default), a whole package (lintr::lint_package()), or a whole project (lintr::lint_dir()). lintr is installed from CRAN, not GitHub HEAD.check-formatting.ymlrunsair format --check -- <path>via posit-dev/setup-air. Air is Posit’s R formatter, a Rust binary, so there is no R session to start. It complementslint-changed-lines.yml: Air settles layout, lintr decides the rest of R style. Check-only – it never rewrites the branch. Adopting it in a repo that is not already Air-formatted is a large first-pass diff; commit anair.tomlfirst (empty is enough), runair formatand commit that rewrite on its own, then enable the check.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.spellcheck.ymlspellchecks an R package’s prose with the {spelling} package, treating the package’s owninst/WORDLISTas the accepted vocabulary. It readsDESCRIPTION’sTitleandDescriptionfields,man/*.Rd, vignette sources (.Rmd,.qmd,.Rnw), and rootREADME/NEWS/CHANGES/indexMarkdown – so a Quarto site’s other pages, and any repo that is not an R package, are out of its reach. It replaces the bespokecheck-spelling.yamlseveral lab repos carry, and needs no wordlist migration, since that file is {spelling}’s own format.check-typos.ymlspellchecks those remaining files with crate-ci/typos: a Quarto site’s non-vignette pages,CONTRIBUTING.md-class Markdown, YAML, code comments, and repositories that are not R packages. Diff-scoped by default (only lines a PR adds, plus filename typos on paths the PR added or renamed), so a first run over an existing repo does not reflag years of drift; passbase-ref: allto scan the whole tree.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). Setexamples-coverage: truefor a second job that runs examples and vignettes.\dontrun{}does not run underR CMD checkby default (only with--run-dontrun, which--as-crandoes not imply);\donttest{}is skipped unless--as-cranor--run-donttest. That job does not upload to Codecov, produce a JUnit report, or applymin-coverage. On failure it uploadsexamples-coverage-test-failures. The percent appears in the log and step summary. Enabling it adds a check named<caller job> / Examples and vignettes coverage. Setmin-coverageto fail the tests job when coverage falls below a chosen percent (empty/off by default). It does not apply to the examples job.check-extra.ymlruns three R-package checks thatR CMD checkpasses over: warnings as errors on examples, tests, and vignettes; tests in random order with a logged seed; and aREADME.Rmdrender that also fails when the knittedREADME.mdis stale. Each check is its own job so every failure surfaces at once.r-cmd-check.ymlrunsR CMD checkacross an OS x R-version matrix (r-lib’s 5-way default). A second caller job withhard: trueruns a Depends/Imports/LinkingTo-only check on pull requests, withcache: falseso a restored pak cache cannot silently contain Suggests.
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, study/participant identifier literals, 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 non-blocking prose checks only annotate.
- 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. Repos with custom build sequences can keep a bespoke build workflow and still use preview-deploy.yml and cleanup-pr-previews.yml by uploading the three-file pr-preview-site artifact (site/, meta/pr-number.txt, meta/action.txt). See Partial adoption and artifact contract.
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. A mention inside a code span, fence, or blockquote is not an invocation: a cheapmention-filterjob runsdetect-bot-mentionand withholds the agent job (gha#554).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 (GitHub suppressespull_requestruns for conflicting PRs; gha#859). The model job’sGITHUB_TOKENhas no write scopes (contents/pull-requests/issues/actions/checks: read); jobs that holdpull-requests: write/issues: writenever invoke the model (gha#580). The caller still grantsactions: read:post-reviewdownloads the packed artifact, and apermissions:block sets unspecified scopes to none. The caller is also asked to grantchecks: read, whichGET .../commits/{ref}/check-runsneeds andactions: readdoes not cover (gha#829). Grant it, and keep it granted. It is optional under the currently-tagged@v2(which no longer requests it, per gha#832) but required by the model job once you adopt@v3. The model job briefly gave the scope up, because a called workflow cannot request a permission its caller lacks, so requesting one startup-fails every caller without it (gha#831). The scope is back on the model job inv3, now that the caller-side grant above is established practice – a caller already granting it moves to@v3at no cost.
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. Enabling issue_comment is required for either comment path, since dispatch-on-comment only ever runs on that event. If you have disabled claude.yml, enable it and also set the repository variable CLAUDE_AGENT_DISABLED to true, so an @claude review mention dispatches a review from the stub rather than reaching a workflow that no longer answers it.
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.
cursor-code-review.yml queues a Cursor Bugbot review through the Enterprise Bugbot API. It is not a mention bot: there is no cursor.yml counterpart. Team and individual Cursor plans should enable Bugbot in the dashboard (automatic reviews, or a PR comment cursor review / bugbot run) instead of this workflow. A Team-plan CURSOR_API_KEY fails the queue step with HTTP 401 Invalid Team API Key (gha#601); do not debug caller secret wiring for that signature. Add cursor to ai-code-review.yml’s agents input when CURSOR_API_KEY is set and the caller stub is installed.
opencode-code-review.yml runs the opencode CLI headless as a read-only reviewer: the diff arrives as an attachment, the agent reads the checkout for context, and its review is posted by the workflow itself. There is no opencode.yml mention bot yet (tracked in gha#586’s deferred follow-ups), so reviews arrive via pull_request, ai-code-review.yml dispatch, or manual workflow_dispatch. Add the OPENCODE_API_KEY secret (OpenCode Zen) and add opencode to ai-code-review.yml’s agents input.
All agent workflows align with upstream action standards (such as anthropics/claude-code-action v1.0.191 and run-gemini-cli v0.1.22, alongside the google-antigravity Python SDK) for trigger policies, permission isolation, and secret handling. In particular, central workflows (claude.yml, claude-code-review.yml) and the review attempt composite action (run-claude-review-attempt) were audited against upstream reference templates in anthropics/claude-code-action:
- Action input forwarding. Upstream inputs such as
claude_code_oauth_token,anthropic_api_key,show_full_output, andallowed_botsare forwarded cleanly, withuse_sticky_comment: 'false'hardcoded inrun-claude-review-attemptso each push surfaces fresh PR activity. - Permission isolation. In agent mode, review runs enforce read-only execution by disallowing git-write tools (
git add,git commit,git rm,git push, andgh pr comment) while allowlisting inline comments and read-only tools (gh pr view,gh pr diff,WebFetch,WebSearch). - Execution guards. Single-turn verdicts are requested in system prompt addenda, while pre-flight credential checks and post-run execution checks gracefully handle unconfigured secrets or exhausted quota (#395).
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, falling through to the next candidate when one can’t be dispatched (no API key or token secret configured for it, or its review workflow file missing or disabled in the caller repo) as well as an agent run that fails or is cancelled during execution (gha#362, gha#444).
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.