diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml index 2f7eee1a..104050fe 100644 --- a/.github/workflows/claude-code-review.yml +++ b/.github/workflows/claude-code-review.yml @@ -9,8 +9,11 @@ name: Claude Code Review (reusable) # claude.yml can re-dispatch a review after an @claude run pushes commits. # The stub passes the dispatch's pr_number through as the `pr-number` input. # -# This workflow is intentionally read-only: its tools grant no git push, so it -# never pushes a fix and `cancel-in-progress: true` is safe. +# This workflow is read-only: it always runs claude-code-action in agent mode +# (track_progress: false) so git write tools are never in ALLOWED_TOOLS. +# Tag mode hardcodes git write tools regardless of --disallowedTools, which +# caused unauthorized commits (serodynamics PR #175, commit 02af72b). Agent +# mode fixes this; cancel-in-progress: true is safe because no push can occur. on: workflow_call: @@ -42,6 +45,16 @@ on: if you loosen that gate to review bot-authored pushes directly. type: string default: 'github-actions[bot]' + track-progress: + description: >- + Enable tag mode (track_progress: true) for pull_request events so the + reviewer posts a live tracking comment and can use the inline-comment + tool. Defaults to false (agent mode), which is safe but produces + summary-only reviews. Set to true only after anthropics/claude-code-action#1415 + ships a read_only input — until then, tag mode grants git write tools + and the reviewer may push commits to the branch. + type: boolean + default: false show-full-output: description: >- Show the full JSON output from Claude Code (passed through to @@ -323,10 +336,10 @@ jobs: # allowlist gap, not a GitHub-App permission scope issue — the tool # ships with the action; it just has to be enabled). Mirrors # anthropics/claude-code-action's own pr-review workflows. Additive: - # the CLI merges this with the action's base allowlist, so tag mode's - # tracking-comment tool is preserved. The tool is only initialized in - # tag mode (pull_request); dispatched/agent-mode runs still fall back - # to the summary, per the prompt below (claude-code-action #635). + # the CLI merges this with the action's base allowlist. The + # inline-comment tool is not initialized in agent mode + # (claude-code-action #635); the prompt falls back to a + # summary-only review when it is unavailable. # ...and keep this a REVIEW-ONLY run. Even with a read-only contents # token, tag mode hands the reviewer git add/commit/rm and the action's # default prompt tells it to commit+push, so it flails through @@ -437,15 +450,22 @@ jobs: explicit `### Verdict` status line required above — restating that the prior verdict stands is not itself the status line. ${{ inputs.prompt-addendum }} - # `track_progress` forces tag mode, which guarantees a tracking - # comment even when the plugin scores the PR below its post - # threshold. The action REJECTS `track_progress` for - # workflow_dispatch, so gate it on event_name: tag mode for - # pull_request, agent mode (possibly silent) for dispatched runs. - # Tag mode also initializes the inline-comment tool; agent mode does - # not (claude-code-action #635), so the prompt above falls back to a - # summary-only review on dispatched runs. - track_progress: ${{ github.event_name == 'pull_request' && 'true' || 'false' }} + # Agent mode (false) by default; tag mode (true) when the caller + # opts in via the track-progress input on pull_request events. + # The action rejects track_progress on workflow_dispatch regardless. + # + # WARNING: tag mode hardcodes git write tools into ALLOWED_TOOLS + # regardless of --disallowedTools (the action's TypeScript sets the + # env var directly, overriding the CLI flag). This caused the + # reviewer to push commits to PR branches (serodynamics PR #175, + # commit 02af72b). Only enable tag mode after + # anthropics/claude-code-action#1415 ships a read_only input. + # In agent mode, ALLOWED_TOOLS comes solely from claude_args so + # no git write tools are ever available; the trade-off is + # summary-only reviews (no live tracking comment or inline-comment + # tool — see claude-code-action #635). The "Post review comment" + # step below compensates when running in agent mode. + track_progress: ${{ inputs.track-progress && github.event_name == 'pull_request' && 'true' || 'false' }} # Fresh tracking comment per run so each push surfaces as new PR # activity; the collapse step below folds priors up as OUTDATED. use_sticky_comment: 'false' @@ -540,21 +560,20 @@ jobs: gh pr comment "$PR_NUMBER" --repo "$REPO" --body "> [!WARNING] > **Claude review skipped — API quota exhausted.** The \`CLAUDE_CODE_OAUTH_TOKEN\` account's API quota is exhausted. Re-trigger the review by pushing a new commit or re-running the workflow once the quota resets. [View run]($RUN_URL)" - # In agent mode (workflow_dispatch), display_report writes Claude's output - # to the GitHub Actions step summary — not to the PR. Tag mode - # (pull_request) creates a tracking comment automatically; agent mode does - # not. Post the review manually here so dispatched reviews are visible on - # the PR instead of silently discarded. Closes #87. - - name: Post review comment for dispatched run + # In agent mode, display_report writes Claude's output to the step + # summary, not to the PR. Post it manually here. Tag mode auto-posts + # the tracking comment on pull_request events, so skip this step then + # (it would duplicate the comment). Always run on workflow_dispatch + # since the action rejects track_progress there regardless. Closes #87. + - name: Post review comment if: >- steps.claude-review.outcome == 'success' && steps.fail-check.outputs.quota_exhausted != 'true' && - github.event_name == 'workflow_dispatch' && - inputs.pr-number != '' + !(inputs.track-progress && github.event_name == 'pull_request') env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} REPO: ${{ github.repository }} - PR_NUMBER: ${{ inputs.pr-number }} + PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr-number }} RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} EXECUTION_FILE: ${{ steps.claude-review.outputs.execution_file }} run: | @@ -595,8 +614,8 @@ jobs: # pushes' review comments too, or they linger now that dispatched runs can # succeed (allowed_bots, above). The run_id→path match already scopes this # to this workflow's comments, so running it on dispatched reviews is safe. - # The "Post review comment for dispatched run" step above ensures that - # dispatched runs always leave a comment with an `actions/runs/` link, + # The "Post review comment" step above ensures that dispatched and + # pull_request runs always leave a comment with an `actions/runs/` link, # so this collapse step can match and fold it in future rounds. - name: Collapse previous Claude review comments if: >- @@ -633,16 +652,14 @@ jobs: done # When this run is canceled (e.g. cancel-in-progress fires because a newer - # commit — or a re-dispatched review of the same PR — superseded it), tag - # mode's tracking comment is left stuck showing an unfinished "review in - # progress". Explain the cancellation in the - # comment, then fold it (minimize as OUTDATED) so the PR doesn't carry a - # perpetual in-progress review. Tag mode only runs on pull_request, so a - # tracking comment only exists there. The tracking comment is THIS run's - # claude[bot] comment, identified by the run it links to — the same - # matcher the collapse step uses. + # commit superseded it), any tracking comment left by a tag-mode run + # (track-progress: true) would be stuck showing "review in progress". + # In agent mode (the default), no tracking comment is created, so this + # step is a no-op — kept as a safety net for tag-mode opt-in callers. + # The comment is identified by its run ID link — the same matcher the + # collapse step uses. - name: Explain and fold tracking comment when canceled - if: cancelled() && github.event_name == 'pull_request' + if: cancelled() env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} REPO: ${{ github.repository }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 2daead02..a1056695 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,13 @@ below with migration steps. eol=lf`, explicit for `*.sh`) and union-merges `CHANGELOG.md` so two sibling PRs appending entries under `## [Unreleased]` merge cleanly instead of conflicting on adjacent insertions (mirrors `d-morrison/ai-config`). +- **`claude-code-review` gains a `track-progress` input** (#134). Consumers + can now opt into tag mode (live tracking comment + inline-comment tool) by + setting `track-progress: true`. The default remains `false` (agent mode), + which is safe — agent mode never grants git write tools, preventing the + review bot from pushing commits to PR branches. Only set `track-progress: + true` once [anthropics/claude-code-action#1415](https://github.com/anthropics/claude-code-action/issues/1415) + ships a `read_only` input; until then, tag mode exposes git write tools. ### Changed @@ -91,6 +98,18 @@ below with migration steps. (ambiguity, meaning-changing grammar, broken links or markup, factual errors). This cuts review-round churn from cosmetic wrapping nits. +### Fixed + +- **`claude-code-review` no longer pushes unauthorized commits to PR + branches** (#134). Tag mode (`track_progress: true`) in `claude-code-action` + hardcodes git write tools into `ALLOWED_TOOLS` regardless of `--disallowedTools`, + causing the review bot to commit and push during review runs (confirmed: + serodynamics PR #175, commit `02af72b`). The workflow now defaults to agent + mode, which builds `ALLOWED_TOOLS` solely from `claude_args` with no git write + tools. Trade-off: reviews are summary-only (no inline comments) in the default + mode; opt into tag mode with the new `track-progress` input when the upstream + fix is available. + ## [2.0.0] - 2026-06-25 ### Breaking diff --git a/README.md b/README.md index f7f0e8b6..91bc67b7 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ Pin to `@v1` (a moving major tag updated as fixes land). Do not reference | `check-news.yml` | Enforce a `NEWS.md` changelog entry on PRs (wraps `UCD-SERG/changelog-check-action`) | `changelog` | | `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` | | `claude.yml` | Agent-mode Claude Code bot: responds to `@claude` mentions, edits files, opens/updates PRs | `setup-r`, `install-quarto`, `use-renv`, `apt-packages`, `pip-packages`, `checkout-submodules`, `link-skills`, `eager-pr`, `prompt-addendum`, `webfetch-allowlist-url`, `reviewer` | -| `claude-code-review.yml` | Read-only Claude PR review (runs the `code-review` plugin; inline findings on `pull_request` runs, consolidated summary on dispatched runs) | `pr-number`, `prompt-addendum`, `checkout-submodules`, `allowed-bots`, `apt-packages`, `pip-packages` | +| `claude-code-review.yml` | Read-only Claude PR review (runs the `code-review` plugin; inline findings when `track-progress: true`, consolidated summary otherwise) | `pr-number`, `prompt-addendum`, `checkout-submodules`, `allowed-bots`, `track-progress`, `apt-packages`, `pip-packages` | | `quarto-publish.yml` | Render a Quarto site and deploy it to GitHub Pages | `path`, `setup-r`, `r-packages`, `use-renv`, `tinytex`, `apt-packages`, `output-dir`, `checkout-submodules`, `pre-render-artifact`, `pre-render-artifact-path`, `deploy` | | `preview.yml` | Build half of the PR-preview family: render a Quarto site in the (possibly fork) PR context and upload it + PR metadata as an artifact (read-only) | `path`, `r-version`, `apt-packages`, `use-renv`, `install-package`, `setup-chrome`, `submodules`, `render-profile` | | `preview-deploy.yml` | Deploy half: on `workflow_run` completion of the build, publish the artifact to `gh-pages` and comment the preview link (base-repo context) | — | @@ -134,7 +134,7 @@ says so. | Feature | Action argument | Caller-configurable? | |---|---|---| -| Live progress tracking comment on the PR | `track_progress` | No — hardcoded to `'true'` on `pull_request` events in `claude-code-review.yml` (`'false'` otherwise); not a `workflow_call` input, so callers cannot override it. Not used in `claude.yml` (agent mode manages its own progress comments). | +| Live progress tracking comment on the PR | `track_progress` | Yes — driven by the `track-progress` input of `claude-code-review.yml` (default `false`; tag mode with tracking comment and inline-comment tool when `true`, agent/summary-only mode when `false`). Not used in `claude.yml`. See `track-progress` warning in the inputs table: tag mode exposes git write tools until anthropics/claude-code-action#1415 lands. | | Full Claude SDK output in the job log | `show_full_output` | Yes — driven by the `show-full-output` input of `claude-code-review.yml` (note the hyphen; off by default, turn on to diagnose silent auth / quota failures). Not surfaced in `claude.yml`. | | Resume a prior session | `session_id` (internal step output of `anthropics/claude-code-action`) + `--resume` in `claude_args` | No — neither reusable workflow declares `session_id` as a `workflow_call` output, so session resume is not available to consumers of `claude.yml` or `claude-code-review.yml`. | diff --git a/website/reference/claude-code-review.qmd b/website/reference/claude-code-review.qmd index b4586410..b79828ab 100644 --- a/website/reference/claude-code-review.qmd +++ b/website/reference/claude-code-review.qmd @@ -15,6 +15,7 @@ keep the stub named `claude-code-review.yml` (or set `claude.yml`'s `review-work | `prompt-addendum` | string | `''` | Repo-specific review guidance appended to the plugin prompt. | | `checkout-submodules` | boolean | `false` | Check out submodules so the reviewer can read their contents. Private submodules require the `SUBMODULES_TOKEN` secret. | | `allowed-bots` | string | `'github-actions[bot]'` | Comma-separated bot actors the reviewer accepts, so a dispatched review is not rejected as non-human. | +| `track-progress` | boolean | `false` | Enable tag mode: live tracking comment + inline-comment tool on `pull_request` events. Default `false` (agent/summary-only mode) is safe. **Only set `true` after [anthropics/claude-code-action#1415](https://github.com/anthropics/claude-code-action/issues/1415) ships a `read_only` input** - until then, tag mode exposes git write tools and the reviewer may push commits to the branch. | | `show-full-output` | boolean | `false` | Show the full JSON output from Claude Code. Turn it on to diagnose a review that fails inside the model call. | | `apt-packages` | string | `''` | Space-separated system packages to `apt-get install` before the review runs (e.g. `maxima` for a computer algebra system). Empty to skip. | | `pip-packages` | string | `''` | Space-separated pip packages to install with `pip3 --break-system-packages` before the review runs (e.g. `sympy`). Empty to skip. | @@ -73,6 +74,7 @@ jobs: # with: # checkout-submodules: true # SUBMODULES_TOKEN secret only for private submodules # allowed-bots: 'github-actions[bot],claude' # accept more bot actors (default: github-actions[bot]) + # track-progress: true # tag mode: live tracking comment + inline comments (unsafe until anthropics/claude-code-action#1415 ships) # apt-packages: maxima # system packages for the reviewer's Bash tool, e.g. a CAS # pip-packages: sympy # pip packages, e.g. a computer-algebra system for math-heavy repos # prompt-addendum: | diff --git a/website/workflows.qmd b/website/workflows.qmd index 48ac3b71..58eac761 100644 --- a/website/workflows.qmd +++ b/website/workflows.qmd @@ -21,7 +21,7 @@ each workflow links to its reference page, with full input tables and a copy-pas | [`check-equation-renders.yml`](reference/check-equation-renders.qmd) | Crawl the build artifact with a headless browser and fail on equations MathJax can't render | `fail` | | [`cleanup-pr-previews.yml`](reference/cleanup-pr-previews.qmd) | Housekeeping: delete preview directories for closed PRs | `preview-dir` | | [`claude.yml`](reference/claude.qmd) | Agent-mode Claude Code bot: responds to `@claude`, edits files, opens/updates PRs | `setup-r`, `install-quarto`, `use-renv`, `apt-packages`, `pip-packages`, `checkout-submodules`, `reviewer` | -| [`claude-code-review.yml`](reference/claude-code-review.qmd) | Read-only Claude PR review | `pr-number`, `prompt-addendum`, `checkout-submodules`, `allowed-bots`, `apt-packages`, `pip-packages` | +| [`claude-code-review.yml`](reference/claude-code-review.qmd) | Read-only Claude PR review | `pr-number`, `prompt-addendum`, `checkout-submodules`, `allowed-bots`, `track-progress`, `apt-packages`, `pip-packages` | | [`bump-submodule.yml`](reference/bump-submodule.qmd) | Update a submodule to upstream HEAD and open a PR when the pointer moves | `submodule-path`, `remote-branch`, `base-branch`, `pr-branch` | | [`sync-shared-fragments.yml`](reference/sync-shared-fragments.qmd) | 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` |