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.