claude.yml

Run the agent-mode Claude Code bot. It responds to @claude mentions, edits files, and opens or updates pull requests. It pairs with claude-code-review.yml: when Claude pushes commits (or an @claude review mention fires), it dispatches the review workflow via workflow_dispatch.

A mention counts as a review request when review follows @claude directly, after punctuation, or after a polite lead-in, and the request then ends its line – @claude review, @claude, review, @claude please review, @claude can you review this?, @claude re-review. The keyword may take an object, but only one pointing back at the pull request itself (@claude review this PR, @claude please review the latest changes).

Anything else is treated as an ordinary request for the agent: a mention that merely contains the word (@claude the review workflow is broken, can you fix it?), and equally one that asks for something to be examined (@claude can you review this and fix the failing test?). A misfire routes the comment to the read-only reviewer and suppresses the agent’s own reply, so the matcher errs toward answering rather than dispatching. Quoted lines are skipped, so quote-replying to a past request does not dispatch a fresh review.

It also runs unattended on workflow_dispatch/schedule events (e.g. a periodic skill invocation with no human comment to react to). The caller’s on: needs one of those triggers, and its job-level if: gate needs widening too, or the caller-side check itself skips the job before this reusable workflow ever runs (see the commented example in examples/claude.yml). Both events are trusted by construction: workflow_dispatch requires the dispatching actor to have repo write access, and a schedule trigger has no triggering actor at all, only existing because someone with write access committed its cron: config to the default branch. So the job’s @claude-mention/author-association gate doesn’t apply to either. These runs get no triggering issue/PR, so no branch or draft PR is set up and no reply comment is posted; use the prompt-addendum input to supply the actual task, and have it use whatever tools claude-args grants to persist output (e.g. gh issue create).

Inputs

Input Type Default Description
setup-r boolean true Install R (pandoc plus setup-r) and the package’s dependencies before running Claude.
install-quarto boolean false Install Quarto (with tinytex) so Claude can render .qmd files.
apt-packages string '' Extra apt packages to install before R/Quarto setup. Empty to skip.
pip-packages string '' Python packages to install with pip3 --break-system-packages before Claude runs. Empty to skip.
use-renv boolean false Restore dependencies with renv instead of DESCRIPTION-based setup-r-dependencies.
r-extra-packages string (long; see workflow file) Extra packages for setup-r-dependencies (ignored when use-renv is true). Defaults to a devtools/roxygen2/lintr/spelling/rcmdcheck set plus local::..
renv-cache-version string '1' renv cache-version passed to setup-renv; bump to invalidate the cached library.
checkout-submodules boolean false Check out submodules. Private submodules require the SUBMODULES_TOKEN secret.
link-skills boolean false Expose the caller repo’s top-level skills/ directory to Claude as project skills. A no-op on PR-triggered runs; commit the symlink to the default branch instead.
eager-pr boolean false On an issue trigger, open the draft PR up front instead of only at the end. Empty PRs are closed automatically.
reviewer string 'd-morrison' GitHub user re-requested as reviewer when Claude pushes commits to a PR.
review-workflow-file string 'claude-code-review.yml' Workflow file in the caller repo dispatched to review Claude’s commits.
mark-ready-for-review boolean true When Claude finishes with committed code, take its draft PR out of draft mode. Set false to leave PRs as drafts.
prompt-addendum string '' Repo-specific instructions appended to Claude’s prompt.
webfetch-allowlist-url string '' URL of a newline-delimited hostname allowlist; each host becomes a WebFetch(domain:<host>) grant. Empty to disable WebFetch.
use-ai-config boolean true Install the Morrison-Lab/ai-config plugin (ai-config@Morrison-Lab), giving the agent the lab’s shared skills, slash commands, and standing workflow conventions. Set false to opt out. Distinct from link-skills, which exposes the calling repo’s own skills/ directory; the two compose.
plugin-marketplaces string '' Extra newline-separated Claude Code plugin marketplace Git URLs, added on top of the Morrison-Lab/ai-config.git marketplace use-ai-config adds. Pair with plugins below; empty adds none.
plugins string '' Extra newline-separated plugin refs to install (<plugin>@<marketplace-name>), added on top of whatever use-ai-config installs. Each marketplace name must match the name that marketplace declares in its own .claude-plugin/marketplace.json, not the URL given in plugin-marketplaces.
claude-args string (long; see workflow file) Args passed to claude-code-action (allowed/disallowed tools). Defaults to a read-and-commit toolset with all git push and write-API forms denied.
runs-on string 'ubuntu-latest' Runner for the job.
report-cost boolean true Surface the run’s dollar cost (total_cost_usd): appended to whichever comment this workflow already posts, or as its own standalone comment on the one path that posts none otherwise. See README.md’s feature-parity table for the upstream source citation. Set false to suppress it.

Secrets

Secret Required Description
CLAUDE_CODE_OAUTH_TOKEN no OAuth token for Claude Code (Claude Max plan).
ANTHROPIC_API_KEY no Direct Anthropic API key (or GitHub App via /install-github-app), used instead of CLAUDE_CODE_OAUTH_TOKEN.
SUBMODULES_TOKEN no Read access to private submodules.
WORKFLOW_TOKEN no PAT or App token with contents:write plus workflows:write, needed only to push changes under .github/workflows/. Falls back to GITHUB_TOKEN when unset – and when that fallback is rejected, the run reports an error naming this secret and posts the unpushed commits to the thread as a git format-patch.

Permissions

Grant contents: write, pull-requests: write, issues: write, id-token: write, and actions: write, and add either the CLAUDE_CODE_OAUTH_TOKEN or ANTHROPIC_API_KEY secret.

Example

# Copy to .github/workflows/claude.yml in your repo.
# NOTE: requires either CLAUDE_CODE_OAUTH_TOKEN (Claude Max plan) or
# ANTHROPIC_API_KEY (direct API / GitHub App via /install-github-app). The
# calling job grants write permissions so Claude can push branches and open PRs.
# Pass secrets explicitly (below) rather than via `secrets: inherit`: GitHub
# only inherits secrets into a reusable workflow owned by the same org/user, so
# a cross-owner caller (e.g. a UCD-SERG-org repo calling this d-morrison
# user-owned workflow) inherits an empty token.
name: Claude Code

on:
  issue_comment:
    types: [created]
  pull_request_review_comment:
    types: [created]
  issues:
    # `assigned` lets an issue that already mentions @claude kick off a run when
    # it's assigned (handy with eager-pr). The trusted-author gate keys on the
    # issue *author*, so this only fires for issues opened by a collaborator.
    types: [opened, assigned]
  pull_request_review:
    types: [submitted]

jobs:
  claude:
    # Cheap caller-side gate: only invoke the reusable workflow when an @claude
    # mention is present AND the author is trusted (OWNER/MEMBER/COLLABORATOR),
    # so an untrusted commenter's mention doesn't even spawn the
    # reusable-workflow run. This mirrors the reusable workflow's own
    # trusted-author gate as defense-in-depth.
    if: |
      (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude') && contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)) ||
      (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude') && contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)) ||
      (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude') && contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.review.author_association)) ||
      (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')) && contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.issue.author_association))
    permissions:
      contents: write
      pull-requests: write
      issues: write
      id-token: write
      actions: write # dispatch the review workflow via `gh workflow run`
    uses: Morrison-Lab/gha/.github/workflows/claude.yml@v2
    secrets:
      CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} # Max-plan OAuth; empty when using API key
      ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} # direct API key; empty when using OAuth
      SUBMODULES_TOKEN: ${{ secrets.SUBMODULES_TOKEN }} # optional; empty when unset
      WORKFLOW_TOKEN: ${{ secrets.WORKFLOW_TOKEN }} # optional; for editing .github/workflows
    # with:
    #   setup-r: true               # default; set false for non-R repos
    #   install-quarto: true        # for Quarto books
    #   use-renv: true              # restore deps with renv instead of DESCRIPTION
    #   apt-packages: jags libglpk-dev poppler-utils  # system libs deps need
    #   pip-packages: sympy         # pip3 --break-system-packages
    #   checkout-submodules: true   # SUBMODULES_TOKEN secret only for private submodules
    #   link-skills: true           # expose this repo's skills/ as @claude project skills
    #   eager-pr: true              # open the draft PR up front (Copilot-style)
    #   mark-ready-for-review: false # keep Claude's PRs as drafts
    #   reviewer: d-morrison        # re-requested when Claude pushes to a PR
    #   report-cost: false          # suppress the dollar-cost comment (default true)
    #   webfetch-allowlist-url: https://raw.githubusercontent.com/d-morrison/stats-allowlist/main/allowlist.txt
    #   use-ai-config: false        # skip the Morrison-Lab/ai-config plugin (installed by default)
    #   plugin-marketplaces: https://github.com/<owner>/<repo>.git   # further plugin sources
    #   plugins: <plugin>@<marketplace-name>
    #   prompt-addendum: |
    #     This is the <name> R package. Before committing, run
    #     lintr::lint_package(), spelling::spell_check_package(), and
    #     devtools::test(); add a NEWS.md bullet for user-facing changes.

See examples/claude.yml for the full caller stub.