Migrate claude.yml (@claude agent) to Morrison-Lab/gha's claude.yml@v2 - #111
Conversation
|
Replaces the hand-rolled workflow with a caller stub targeting the reusable workflow, which builds in the author gate and write permissions the bespoke copy lacked. Closes the two gaps tracked in #99: - pull-requests: read -> write, so Claude can actually open/update PRs. - adds an OWNER/MEMBER/COLLABORATOR author check on @claude triggers. Also installs Quarto and R so Claude can render and check its own edits; the hand-rolled workflow installed neither. Refs #100
- setup-r: false, against gha's default. This repo's own
copilot-setup-steps.yml already recorded the same conclusion ("we don't
have any R code to run yet"), and it still holds: zero .qmd files contain
an R chunk and _quarto.yml is html-only. Enabling it would also run
`local::.` on every invocation -- an install nothing on main exercises,
against a DESCRIPTION with `LazyData: true` and no data/ directory -- and
a failure there happens before Claude runs, killing every @claude run.
- Document the tag-mode to agent-mode switch and the tool-surface narrowing
it brings. gha sets a `prompt:`, and its default claude-args REPLACES the
allowlist rather than extending it, denying git push, gh pr create, and
the write forms of gh api.
- Correct the `actions: write` comment: the review dispatch it enables only
resolves once #110 adds a workflow_dispatch trigger to
claude-code-review.yml.
- Correct the enumerated upstream defaults. `reviewer` defaults to
'd-morrison' rather than being unset, so d-morrison is re-requested on
every Claude push -- new behaviour worth naming.
- Carry across gha's WORKFLOW_TOKEN caveat (a PAT push fires other
push-based workflows) and its upside (it fires `synchronize`, so Claude's
commits would get reviewed even without the dispatch path).
- Note the retained `assigned` trigger's changed cost, the unconditional
TinyTeX install, and the omission of SUBMODULES_TOKEN.
- Restate the `secrets: inherit` rule as GitHub documents it (same
organization or enterprise).
Refs #100
The previous comment said "Not set in this repo" and framed the forwarding as aspirational. That was true when measured and is no longer: a WORKFLOW_TOKEN secret was added at the UCD-SERG org level on 2026-08-24 at 16:35Z with visibility "all repositories", and it is reachable from this repo. Restates the two consequences as live rather than hypothetical, and adds the point that made the forwarding worth keeping either way: a reusable workflow only sees the secrets its caller passes, so an org secret this block omitted would be invisible to gha's workflow regardless of its visibility. Refs #100
Self-review (fallback under
|
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. |
Three of my own factual claims were wrong. Corrected: - Removed an invented failure mode. The comment claimed `LazyData: true` with no data/ directory is "a known source of R CMD INSTALL complaints" that would "kill every @claude invocation". Measured: R CMD INSTALL completes with no warning and no error, and R CMD build silently drops the field. The setup-r: false decision stands on the rest of its rationale, which was independently verified; it did not need an invented risk and should not have carried one. - Corrected the WORKFLOW_TOKEN cost claim. It named publish.yml and check-spelling.yaml as workflows a PAT push would fire; both filter their push trigger to main, and gha never pushes to main, so neither fires that way. The real effect is four pull_request workflows gaining a `synchronize` run -- which is the same mechanism the next sentence called the benefit, so the comment counted one mechanism twice. - Corrected the gh api claim. What the default claude-args denies is three prefix patterns (`-X`, `--method`, `graphql`), not "the write forms": a `-f` call is a POST by default and matches none of them, and prefix matching is defeated by flag order. Filed upstream as Morrison-Lab/gha#616. Also: warn that overriding claude-args replaces the deny list wholesale; note that `gh pr create` is omitted rather than denied; record what setup-r: false gives up (five of six Rscript grants, so Claude can edit R but not lint or test it) and that it also skips pandoc; add review-workflow-file and trusted-bot-logins to the defaults list, with the both-halves caller-mirroring rule on the two that need it; correct report-cost's description; and resolve the eager-pr/dispatch-on-assignee contradiction between two comments. Refs #100
Self-review, round 2 --- three of my own factual claims were wrongSecond adversarial round against I invented a failure mode to justify
|
|
@BugBot review |
PR SummaryHigh Risk Overview Security and permissions: Every Runtime behaviour: Switches from tag mode to agent mode (upstream default tool allowlist and post-step push/PR flow). Reviewed by Cursor Bugbot for commit 4c1406b. Bugbot is set up for automated code reviews on this repo. Configure here. |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 4c1406b. Configure here.
Important
Merge #110 first. gha's reusable
claude.ymldispatches the review workflowvia
workflow_dispatch, and this repo's current bespokeclaude-code-review.ymlhas noworkflow_dispatchtrigger, so that dispatchwould fail until #110 lands. The two PRs touch disjoint files, so there is no
merge conflict --- only an ordering constraint.
Closes #100
Replaces the hand-rolled
.github/workflows/claude.yml, which calledanthropics/claude-code-action@v1directly, with a caller stub forMorrison-Lab/gha's reusableclaude.yml@v2.This closes #99, which is the point
#99 tracks two gaps in the bespoke workflow. Both are closed here, and both are closed by adopting gha's baseline rather than by patching:
pull-requests: readpull-requests: write@claudetriggersOWNER/MEMBER/COLLABORATORgate on all four event typesThe author gate is defense-in-depth rather than the only check: the reusable workflow has its own trusted-author gate, but without the caller-side one the run is only skipped after invoking a workflow that was already granted elevated permissions and passed secrets.
What is preserved
issue_comment,pull_request_review_comment,issues [opened, assigned],pull_request_review [submitted].additional_permissions: actions: read, so Claude can still read CI results on PRs. The reusable workflow sets this internally; I checked rather than assuming:CLAUDE_CODE_OAUTH_TOKENas the auth mechanism.What changes
contents: write,pull-requests: write,issues: write,id-token: write,actions: write. The last is new and is what lets Claude dispatch a review of its own commits --- which is the coupling with Migrate PR review workflows to Morrison-Lab/gha's claude-code-review.yml@v2 #110.install-quarto: true). The hand-rolled workflow installed neither Quarto nor R, so Claude could edit.qmdfiles without being able to render them or check its own work. Note gha's input installs TinyTeX unconditionally with no opt-out; this site is html-only, thoughpublish.ymlandpreview.ymlalready pay that same cost today.setup-r: false), deliberately against gha's default oftrue. See below.prompt:, which putsclaude-code-actionin agent mode. Agent mode skips the action's built-in branch-setup / git-push / response-comment machinery (gha's post-steps do that instead) and ships a defaultclaude-argsthat replaces the effective tool allowlist rather than extending it. Net effect: Bash is narrowed to an enumerated list,git pushis denied in every listed variant, andgh pr createis omitted from the allowlist. This is the largest behavioural change in the diff and is documented in the file. Note thegh apidenials are narrower than they look --- see the round-2 self-review below and claude.yml default claude-args: the gh api deny patterns do not deny writes Morrison-Lab/gha#616.secrets: inherit: GitHub only inherits secrets into a reusable workflow owned by the same org/user, and this is a UCD-SERG repo calling a Morrison-Lab workflow, soinheritwould supply an empty token.WORKFLOW_TOKENis forwarded, and the secret is now liveCorrected since this PR was opened. When first written, this repo had exactly one Actions secret and the
WORKFLOW_TOKENforwarding was aspirational --- the workflow comment said "Not set in this repo". That is no longer true. AWORKFLOW_TOKENsecret was added at the UCD-SERG org level on 2026-08-24 at 16:35:37Z, visibility "all repositories", and it is reachable here:It authenticates as a user PAT and can push a commit touching
.github/workflows/, so it genuinely carries theworkflowsscope. The comment in the workflow has been corrected to match. Worth flagging as a category: that was a state claim that expired between measurement and merge, not a mistake in reasoning.Forwarding it explicitly is load-bearing, not decorative. A reusable workflow only ever sees the secrets its caller passes, so an org-level secret this
secrets:block omitted would be invisible to gha's workflow no matter how broad its visibility. That is exactly the defect filed today against two sibling repos --- UCD-SERG/serocalculator#663 and UCD-SERG/serodynamics#297, both gha callers that fail to forward it. This PR avoids that class.Why it matters here specifically:
.github/workflows/is what@claudehas mostly been asked to edit in this repo (#83, #87, #89, #91, #92, #93, #94, #95, #97). Without the token those pushes are rejected, and gha falls back to posting the commits as agit format-patch.Two consequences, now live rather than hypothetical:
GITHUB_TOKEN, a PAT push does trigger other push-based workflows, so Claude's pushes now set off this repo'spush/pull_requestworkflows.synchronize, which aGITHUB_TOKENpush does not --- so Claude's commits get an automatic review even without the dispatch path.SUBMODULES_TOKENis deliberately not forwarded: this repo has no submodules.ANTHROPIC_API_KEYis forwarded but unset; auth is viaCLAUDE_CODE_OAUTH_TOKEN.Why
setup-r: false, against gha's defaultThis is the one place the migration deviates from gha's defaults, and the evidence is the repo's own:
.github/workflows/copilot-setup-steps.ymlline 12: "R and renv setup steps are disabled for now as we don't have any R code to run yet." Itssetup-rblock is commented out for the same reason.grep -rn '```{r' --include='*.qmd' .returns 0 files, and_quarto.ymldeclares onlyformat: html. No render path needs R.setup-rpath runssetup-r-dependenciesincludinglocal::., i.e. it installs this repo as an R package on every invocation, and nothing onmainexercises that today (lint-project.yamlinstalls onlylintr;check-spelling.yamlruns in arocker/tidyversecontainer).Rscript -egrants in gha's default allowlist are dead without R, so Claude can edit R here but cannot lint, document, or test what it edits. An R fix would land unverified, withlint-project.yamlas the first thing to catch a mistake. That is the trade, and the reason to revisit if R work becomes routine.An earlier revision of this PR set
setup-r: trueon the reasoning that the repo "carries an R package skeleton". That reasoning did not engage withcopilot-setup-steps.yml, which had already reached the opposite conclusion on the same repo. Flip it back if R work becomes routine here, after verifying thelocal::.install actually succeeds.Inherited defaults, named rather than left silent
Each of these changes behaviour relative to the hand-rolled workflow, and each is enumerated in a comment in the file rather than set (so the
with:block carries only deviations):use-ai-configtrueMorrison-Lab/ai-configpluginreport-costtruemark-ready-for-reviewtruereviewer'd-morrison'eager-prfalsedispatch-on-assignee'[]'link-skillsfalseskills/hereThe
reviewerone is worth a second look before merge --- it is new behaviour and it is not obvious from the stub. Set it to''to suppress.SUBMODULES_TOKENis deliberately not passed: this repo has no submodules.Not changed
The
on:block is byte-identical to the hand-rolled workflow's, includingissues: [opened, assigned]. Its cost did change ---assignedre-fires a full agent run whenever anyone is assigned to an already-open issue mentioning@claude, and that run now holdscontents: writerather thanread--- so it is documented in the file rather than silently retained. Narrowing the trigger set is a behaviour change this migration did not set out to make.Validation
actionlintandyamllintclean on the new file, and it is ASCII-only.Review status: blocked on an external verdict, not ready
check-pr-fully-clean.pyexits 1 here with "No automated review comments or reviews found". That is accurate.The cause is #105: this repo's hand-rolled
claude-reviewexecutes and posts nothing. A clean reproduction landed on this PR --- run 32751977837,is_error: false,num_turns: 4,total_cost_usd: 0.0520,permission_denials_count: 1, green check, no comment --- written up on #105. This PR touches no workflow file, so the self-modification guard that explains #110's missing review does not apply here. The two PRs went unreviewed for different reasons, and only #110's resolves on merge.Per
self-review-fallback, the fallback is an adversarial self-review (posted separately on this PR), with a cross-vendor reviewer requested in parallel. A human approval is required to merge regardless: themainruleset requires 1 approving review,require_last_push_approval, and thread resolution.The standing rule authorizing this migration is Morrison-Lab/ai-config#2127, merged 2026-08-24 at 16:42Z.