All notable changes to Morrison-Lab/gha are documented here.
This repo uses a moving major tag (v1) for consumers, following the
r-lib/actions convention: the v1 tag
moves forward as non-breaking fixes land, and consumers pin to @v1. Each
release is also tagged with a full vX.Y.Z so a specific point can be pinned
if needed. Breaking changes bump the major tag (v2, …) and are called out
below with migration steps.
-
claude-code-reviewholds PRs to the SERG lab manual, opt-outable via alab-manualinput (#170, #171). The reusable review prompt evaluates a PR against the UCD-SERG lab manual -- coding style, coding practices (including function decomposition/length limits), and repository conventions -- in addition to correctness, so every R/Quarto consumer gets this check without needing aprompt-addendum(it mirrors the wording in this repo's ownCLAUDE.md"Code review guidelines"). It is gated behind alab-manualboolean (defaulttrue, so R/Quarto consumers are unchanged); setlab-manual: falsefor repos the R/tidyverse/Quarto-focused manual doesn't target (a pure GitHub Actions, shell, or docs repo) so the reviewer isn't handed irrelevant guidance. -
claude-code-reviewgrants the reviewerBash(python3 <file>). The review agent could previously only trace a Python script's logic by eye, since its--allowedToolscovered just the inline-comment tool plus the action's base allowlist (Read/Glob/Grep and narrow git-read Bash) -- no way to actually execute the script under review (rme#970). Scoped to running an existing file under the checkout (-c/-mdenied, so inline/module code execution stays blocked) -- same-repo PRs give this jobCLAUDE_CODE_OAUTH_TOKEN/ANTHROPIC_API_KEYas secrets, so unrestrictedpython3:*would have been a real capability widening beyond the existing git-read-only sandbox, not just a git-push question. It can now verify a script's behavior instead of guessing from source alone (#154). -
claude-code-reviewnow honors an explicit review request on a draft PR. A dispatched review (an@claude reviewcomment routed here byclaude.yml,claude.yml's post-push re-dispatch, the issue-trigger draft PR, or a manual dispatch) already bypassed the workflow's draft-skipif:gate, but the code-review skill's own don't-review-drafts stop condition still made the agent refuse ("currently a draft … I will not proceed"), so an explicit@claude reviewon a draft produced a refusal instead of a review. The dispatched-run prompt now overrides that stop condition so an explicitly-requested review runs even on a draft. Automaticpull_requestreviews still skip drafts (theirif:gate never reaches the agent on a draft), so this only widens the dispatched path. -
claude-code-reviewno longer flags cosmetic source-only formatting that renders identically (#261). The review prompt now tells the reviewer to skip raw-source line-wrap position and line-length nits on Markdown/text prose when the rendered output is unchanged, and to flag only genuine prose problems (ambiguity, meaning-changing grammar, broken links or markup, factual errors). This cuts review-round churn from cosmetic wrapping nits.
-
test-coverage-- R-package test coverage with Codecov upload (#147). A newcomposite (
test-coverage/action.yml) and reusable workflow (.github/workflows/test-coverage.yml) that set up R and dependencies, runcovr::package_coverage(), and upload the Cobertura report withcodecov/codecov-action. Adapts the canonicalr-lib/actionstest-coverage.yamlexample into the repo's composite-plus-wrapper shape. Inputs:path(package root, defaults to repo root),install-quarto,extra-packages, andfail-ci-if-error('auto'by default, applying the r-lib heuristic -- fail on non-PR events, and on PRs only when a token is set, since tokenless PR uploads are flaky -- or force'true'/'false'); the optionalCODECOV_TOKENsecret is passed through the caller'ssecrets:block. Seeexamples/test-coverage.ymlfor the caller stub. -
check-equation-renders-- catch equations MathJax can't render in PR previews(#159). A new fourth leg of the PR-preview family: a composite (
check-equation-renders/action.yml) that crawls a built Quarto/HTML site with a headless Chromium (Playwright), lets MathJax finish typesetting each page, and fails on either of the two ways MathJax signals a broken formula: a hard parse error (a[data-mjx-error]node), or an undefined macro, which MathJax renders as literal, unresolved\commandtext rather than a hard error (no error node to find, so the check looks for the raw command name surviving into the rendered output instead). Wired into the family as a reusable workflow (.github/workflows/check-equation-renders.yml) triggered the same way aspreview-deploy.yml(workflow_runon the build workflow's completion), downloading thepr-preview-siteartifact directly rather than depending on the deploy. Motivated by a broken equation (d-morrison/rme#954) that shipped silently: Quarto'shtml-math-method: mathjaxembeds raw TeX unchanged in the static HTML, and MathJax only parses it client-side, so a bad equation produces no warning in the Quarto/pandoc build log. Ships at@v2(too new for the frozen@v1tag), liketest-coverage. Seeexamples/check-equation-renders.ymlfor the caller stub. -
claude-code-reviewacceptsapt-packages/pip-packagesinputs (#161), mirroring the inputsclaude.ymlalready had. Lets a math-heavy consumer repo install a computer algebra system (e.g.apt-packages: maxima,pip-packages: sympy) so the reviewer'sBashtool can symbolically check a derivation instead of eyeballing the algebra. Both default to''(no-op), so this is backward compatible for every existing caller. The install steps for both workflows are now factored into a shared.github/actions/install-packagescomposite rather than duplicated. -
Added
.gitattributes(#163). Normalizes line endings (text=auto eol=lf, explicit for*.sh) and union-mergesCHANGELOG.mdso two sibling PRs appending entries under## [Unreleased]merge cleanly instead of conflicting on adjacent insertions (mirrorsd-morrison/ai-config). -
claude-code-reviewgains atrack-progressinput (#134). Consumers can now opt into tag mode (live tracking comment + inline-comment tool) by settingtrack-progress: true. The default remainsfalse(agent mode), which is safe -- agent mode never grants git write tools, preventing the review bot from pushing commits to PR branches. Only settrack-progress: trueonce anthropics/claude-code-action#1415 ships aread_onlyinput; until then, tag mode exposes git write tools. -
/reviewcomment trigger forclaude-code-review. Commenting/reviewat the start of a PR comment now starts a review of that PR on demand, without routing throughclaude.yml's@claudeagent. The review caller listens for the comment (gated toOWNER/MEMBER/COLLABORATORauthors) and re-dispatches its ownworkflow_dispatchreview, reusing the existing dispatched-review flow -- so the reusableclaude-code-review.ymlis unchanged and consumers get the tag by updating theirexamples/claude-code-review.ymlstub. It's a slash command rather than an@claude reviewmention on purpose: any@claudesubstring also wakesclaude.yml, so the slash command keeps the direct path independent. Works once the workflow is on the default branch (gh workflow runrequires it).
-
claude-code-review's stub guard no longer false-fails complete reviews (#173). The### Verdict-heading check added for the silent-stub problem was too strict: thecode-reviewplugin states its conclusion as aVerdict:label (not the### Verdictheading the prompt requests), and in agent mode it can land in an earlier assistant message than the last -- so every push-triggered review red-X'd (gha#175) even when it posted a full review with a verdict.fail-checknow scans all of the run's assistant text (not just the final block) and accepts a verdict line in any heading/label/bold form (### Verdict,**Verdict:**,Verdict:), and the comment posted back to the PR uses the verdict-bearing block rather than the final one, so a trailing wrap-up message ("I've posted my findings") isn't mistaken for the review body (sparta#594). A genuine stub still states no verdict and still fails, preserving the original intent of catching the silent-stub failure mode (Lacaedemon/sparta#590). -
claude-code-review's pass/fail check now catches stub reviews, not just API errors (#171). The reusable workflow'sfail-checkstep previously only inspectedis_error/subtypeon the SDK result, so a run that reported success while actually exiting on an orchestration placeholder (e.g. "Waiting for both background agents... before proceeding to the next review steps") still posted that placeholder as the finished review and the check stayed green -- the same silent-stub failure mode reported upstream inLacaedemon/sparta#590.fail-checknow also extracts the run's final review text and fails the check if it's empty (including whitespace-only) or missing the### Verdictheading this workflow's own prompt requires of every finished review -- a stub is narration, never a finished review, so it can't contain that heading, which catches every observed stub phrasing without having to enumerate them individually. -
claude-code-reviewno longer pushes unauthorized commits to PR branches (#134). Tag mode (track_progress: true) inclaude-code-actionhardcodes git write tools intoALLOWED_TOOLSregardless of--disallowedTools, causing the review bot to commit and push during review runs (confirmed: serodynamics PR #175, commit02af72b). The workflow now defaults to agent mode, which buildsALLOWED_TOOLSsolely fromclaude_argswith no git write tools. Trade-off: reviews are summary-only (no inline comments) in the default mode; opt into tag mode with the newtrack-progressinput when the upstream fix is available.
quarto-publishnow deploys to thegh-pagesbranch instead of the GitHub Pages artifact (#118, #120). Consumers must set Settings -> Pages -> Source = "Deploy from a branch", branchgh-pages/(root), and grant the callercontents: write(droppingpages: write+id-token: write) -- still required withdeploy: false. This makesquarto-publishcompatible with the branch-based PR-preview family, so a repo can ship both a main site and PR previews. Full migration steps are in thequarto-publishentry under Added.@v1stays frozen on the Pages-artifact deploy for consumers not ready to migrate.
-
Dark mode for the documentation website (#114) --
website/_quarto.ymlnow pairs thecosmolight theme with thedarklydark theme, so the site renders a light/dark toggle in the navbar and respects the reader's system preference. -
Documentation website (#100) -- a Quarto site under
website/that documents every reusable workflow (overview, a per-action reference page with full input tables, permissions, and versioning). It is built and shipped by the repo's own actions:quarto-publishdeploys it to GitHub Pages onmain, and thepreviewfamily renders a per-PR preview. -
previewgains apathinput (#100) -- the project directory to render (the dir holding_quarto.yml), defaulting to the repo root. This bringspreviewto parity withquarto-publishand lets a site that lives in a subdirectory (like this repo'swebsite/) get a PR preview. Backward compatible: existing callers that render the repo root need no change. -
Shared-content sync family (#57) -- keeps guidance shared between repos current in both directions, via two reusable workflows and a shared helper:
-
bump-submodule.yml-- update a named submodule to its upstream HEAD and open a PR when the pointer moves (one direction; e.g. the lab manual tracking.ai-config). -
sync-shared-fragments.yml-- vendor a set of files from an upstream repo, pinned to a commit and recorded in a JSON manifest, and open a PR when they change (the other direction; avoids a recursive mutual submodule). -
open-sync-prcomposite -- the commit-and-open-PR helper both workflows reuse: commits staged changes to a reused automation branch and opens or updates the PR, no-op when nothing changed. First consumers:UCD-SERG/lab-manualandd-morrison/ai-config.
-
-
quarto-publish-- render a Quarto site and deploy it to thegh-pagesbranch, which GitHub Pages serves. A composite (quarto-publish/action.yml) sets up Quarto (optionally R/renv and TinyTeX) and renders a project at a givenpathinto<path>/<output-dir>(default_site). The reusable workflow (quarto-publish.yml) deploys that output togh-pageswithclean-exclude: pr-preview/, so a main-site deploy never wipes the preview family's per-PR sites; it also offers optional submodule init and apre-render-artifactinput so a caller can inject build-time assets (e.g. recorded media) before render. Callers grantcontents: write(still required withdeploy: false, since the deploy job is part of the workflow) and set Pages Source = "Deploy from a branch", branchgh-pages. First consumer:Lacaedemon/sparta(#37).Breaking change for early
@v1adopters (#117). An earlier interim version deployed viaactions/deploy-pagesand needed Pages Source = "GitHub Actions". That is incompatible with the PR-preview family, which is branch-based, so previews 404'd. Switchingquarto-publishto agh-pagesdeploy makes publish and preview consistent. To migrate: (1) set Settings -> Pages -> Source = "Deploy from a branch", branchgh-pages/(root); and (2) change the caller's job permissions frompages: write+id-token: writetocontents: write(seeexamples/quarto-publish.yml). -
PR-preview / publish family (#33) -- centralizes the three-workflow preview pipeline rme carried inline:
-
previewcomposite action +preview.ymlreusable workflow -- build half; renders the Quarto site read-only in the (possibly fork) PR context and uploads it + PR metadata as an artifact. Parameterized for non-rmeconsumers (R version, apt packages, renv on/off, local-package install, Chrome, submodules, render profile). Writes PR metadata after checkout
so
git clean -ffdxcan't wipe it from the artifact (d-morrison/rme#913), and keeps thepreview:pdf/preview:docx/preview:revealjsandclear freezerlabel gates. -
preview-deploy.ymlreusable workflow -- deploy half; onworkflow_runcompletion publishes the artifact togh-pagesin the base-repo context and comments the preview link. Kept split from the build half so untrustedfork code never holds write permissions (the trust boundary).
-
cleanup-pr-previews.ymlreusable workflow -- scheduled housekeeping that deletes preview directories for closed PRs.
-
-
check-phi-- scans pull requests (added lines only; whole tree onpush) for content that looks like PHI: US Social Security numbers, medical record numbers, dates of birth, and PHI-suggestive column headers in delimited data files. Matched values are never printed to the log; false positives are suppressed via aphi-allowline comment or a regex allowlist file. Thephone/emaildetectors are available but off by default. -
CHANGELOG.md(this file) -- records what changes as the@v1tag moves, so consumers can see what they picked up. -
REVDEPS.md-- tracks repos that consume these workflows so breaking changes can be announced. See the file for how to register. -
.github/actions/checkout-submodulescomposite action -- centralizes the submodule-init logic (theSUBMODULES_TOKENinsteadOfrewrite and the anonymous-clone fallback) shared by theclaudeandclaude-code-reviewreusable workflows (#25).
-
Multi-line
run:blocks in the composite actions and reusable workflows now declareset -euo pipefailexplicitly. GitHub already runsshell: bashwith-eo pipefail; the net new protection isnounset(unset-variable typos now fail fast), plus consistency with the rest of the script logic. -
claudeandclaude-code-reviewno longer carry duplicateCheckout submodulessteps; both call the sharedcheckout-submodulesaction instead, so the token-rewrite logic lives in one place (#25). -
check-bibliography-doisnow collects.bibfiles NUL-delimited into a bash array, so bibliography paths containing spaces are passed to the checker as intact single arguments instead of word-splitting (#30). -
claude-code-review's prompt now instructs the reviewer to watch for AI hallucinations -- fabricated functions/arguments/APIs, invented references, DOIs, or URLs, plausible-but-unreal file paths and constants, and comments that describe behavior the code doesn't implement -- and to verify questionable symbols against the codebase rather than assuming they exist (#56). -
claudenow reproduces qwt's late-comment dedup so a follow-up@claudecomment absorbed by a still-running session isn't double-handled by the duplicate run it also queued: a "Skip if this comment was already handled" pre-step bows out when the triggering comment already carries a github-actions 🚀 marker, the agent emits a<!-- claude-absorbed: … -->marker listing the comments it absorbed by polling, and a post-step reacts 🚀 to each so their own queued runs short-circuit. A companion step re-dispatches a review for a late@claude reviewthat a deduped run would otherwise have dropped. Additive -- no consumer input changes (#44, ported from qwt #73/#90/#95). -
claude-code-reviewgained anallowed-botsinput (defaultgithub-actions[bot], previously hard-coded) so a consumer can widen the accepted dispatch actors (e.g.github-actions[bot],claude), and a "Skip self-review when the PR edits this workflow" step that detects (viagithub.workflow_ref) a PR modifying the caller's review workflow and skips the review -- which would otherwise 401 on the action's workflow-validation until merged -- instead of posting a failed check (#45, ported from qwt).
- Example caller stubs now pass secrets explicitly instead of
secrets: inherit. GitHub only inherits org/repo secrets into a reusable workflow owned by the same org/user, so a cross-owner consumer (e.g. aUCD-SERG-org repo calling thesed-morrison-user-owned workflows) inherited an emptyCLAUDE_CODE_OAUTH_TOKENand every@clauderun failed env-validation ("… is required when using direct Anthropic API").examples/claude.ymlandexamples/claude-code-review.ymlnow passCLAUDE_CODE_OAUTH_TOKEN(and the optionalSUBMODULES_TOKEN/WORKFLOW_TOKEN) explicitly, which resolves caller-side and works regardless of owner. Existing consumers copied from the old stubs must make the same change (#49). claude-code-reviewnow setsallowed_bots: github-actions[bot], so the reviewclaude.ymlre-dispatches after an@clauderun pushes commits can actually run. The action's agent mode (used byworkflow_dispatch) blocks bot actors by default, so dispatched reviews previously failed with "Workflow initiated by non-human actor" -- and, having entered the per-PR concurrency group, canceled the parallelsynchronizeauto-review on their way out, leaving the push with no review at all.claude-code-review's "collapse previous review comments" step is no longer gated topull_request, so a dispatched (workflow_dispatch) review that wins the per-PR concurrency race also folds earlier pushes' review comments as OUTDATED instead of leaving them expanded.
-
All third-party actions are now pinned to full commit SHAs (with the human-readable version in a trailing comment), following GitHub's recommended hardening posture. A SHA is immutable, so a re-pointed tag or a compromised upstream can no longer silently change what runs -- most important for the
preview-deployjob, which runs in the base-repo context withcontents: write+pull-requests: write. Added.github/dependabot.yml(github-actionsecosystem, weekly, grouped, covering.github/workflows/and each composite action) so the pins are auto-bumped as upstreams publish releases instead of freezing. First-partyd-morrison/gha/*@v1self-references and theexamples/templates intentionally still track the@v1major tag (#48).
v1 -- initial pilot set
Reusable workflows + composite actions:
check-bibliography-dois-- validate book/article BibTeX entries have resolvable DOIs matching CrossRef metadata.check-links-- lychee link check with bundled config, PR skip-label, and auto-issue onmain.check-non-standard-chars-- detect curly quotes / en-em dashes in.qmdand.Rfiles.check-news-- enforce aNEWS.mdchangelog entry on PRs (wrapsUCD-SERG/changelog-check-action).summary-- AI summary comment on newly opened issues.