Skip to content

Harden summary.yml against shell + prompt injection #57

Harden summary.yml against shell + prompt injection

Harden summary.yml against shell + prompt injection #57

# Automatic Claude Code review of pull requests.
#
# Runs the upstream `code-review@claude-code-plugins` plugin so we pick up
# any improvements to the canonical review skill, then layers a
# Quarto/R-specific addendum on top so the reviewer also catches issues
# particular to this template repo.
#
# Each run posts a fresh review comment; prior reviews are left in place
# so the PR keeps a visible history rather than a rolling sticky.
#
# Skips drafts, Dependabot bumps, and fork PRs (fork PRs can't read repo
# secrets). Project guidance is in CLAUDE.md. Requires the
# CLAUDE_CODE_OAUTH_TOKEN repository secret.
name: Claude Code Review
on:
pull_request:
types: [opened, synchronize, ready_for_review, reopened]
# Optional: Only run on specific file changes
# paths:
# - "src/**/*.ts"
# - "src/**/*.tsx"
# - "src/**/*.js"
# - "src/**/*.jsx"
# Allow claude.yml to dispatch a fresh review (e.g. after an @claude run
# pushes commits, or on an `@claude review` comment). GITHUB_TOKEN pushes
# don't fire `synchronize`, so an explicit dispatch path is needed.
workflow_dispatch:
inputs:
pr_number:
description: 'Pull request number to review'
required: true
type: number
# Serialize reviews per PR: a newer push cancels the in-progress review
# of the now-stale diff so only the freshest review runs (and posts a
# comment). Plain `cancel-in-progress: true` is safe here — unlike the
# rme review workflow, this one is read-only (its allowedTools below
# grant no git push / commit), so it never pushes a fix and therefore
# can't trigger the self-cancellation that rme had to guard against
# with a conditional expression (see d-morrison/rme#817). The
# `|| inputs.pr_number` keeps a workflow_dispatch review in the same
# group as the PR's pull_request reviews so they dedupe.
concurrency:
group: claude-review-${{ github.event.pull_request.number || inputs.pr_number }}
cancel-in-progress: true
jobs:
claude-review:
# workflow_dispatch is fired by claude.yml for a specific PR, so always
# run it — this intentionally bypasses the draft guard below so a review
# fires on the draft PR claude.yml opens for an issue trigger (we want to
# review Claude's draft work early). Otherwise skip drafts, Dependabot
# bumps, and fork PRs (fork PRs can't read repo secrets, so
# CLAUDE_CODE_OAUTH_TOKEN would be empty and the run would fail with a
# noisy red check).
if: |
github.event_name == 'workflow_dispatch' ||
(github.event.pull_request.draft == false &&
github.event.pull_request.user.login != 'dependabot[bot]' &&
github.event.pull_request.head.repo.full_name == github.repository)
runs-on: ubuntu-latest
env:
# Resolve the PR number once: from the pull_request event, or the
# workflow_dispatch input when claude.yml triggered us.
PR_NUMBER: ${{ github.event.pull_request.number || inputs.pr_number }}
permissions:
contents: read
pull-requests: write # post review + inline comments
issues: read
id-token: write # required by claude-code-action for the App-token
# exchange it performs even with OAuth auth
# (without this, the action fails with
# "App token exchange failed: 401 Unauthorized")
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1
submodules: recursive
- name: Claude PR review
uses: anthropics/claude-code-action@v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
plugin_marketplaces: 'https://github.com/anthropics/claude-code.git'
# `claude-code-plugins` is a branch name, not a version tag.
# Intentionally unpinned so we pick up future improvements to the
# upstream code-review skill; pin to a tag if reviews ever start
# changing in surprising ways.
plugins: 'code-review@claude-code-plugins'
# track_progress forces tag mode (guaranteed tracking comment),
# but the action rejects it for workflow_dispatch events
# ("track_progress is only supported for events: pull_request,
# issues, ..."). Gate on event_name: tag mode for pull_request,
# agent mode for dispatched runs. (See d-morrison/rme#818.)
track_progress: ${{ github.event_name == 'pull_request' && 'true' || 'false' }}
prompt: |
/code-review:code-review ${{ github.repository }}/pull/${{ env.PR_NUMBER }}
In addition to the standard checks above, this is the qwt Quarto
website template — small, prose-heavy, and re-used as a template
for downstream books — so also prioritize:
1. **Quarto / prose correctness**
- `.qmd` syntax: chunk options use `#|` directives, fenced
blocks are balanced, cross-refs (`@fig-`, `@tbl-`, `@sec-`)
resolve to defined labels.
- Bullet-list rule: 3+ items use bullets with a blank line above.
- `code-fold: true` is used on chunks where the output (not the
code) is the point, and avoided on tutorial code.
2. **R code**
- Conforms to `.lintr.R` settings (snake_case, line length, etc.).
- No new package dependencies without justification — every
dependency added here lands in every downstream book.
3. **Build hygiene**
- No accidental commits of `_site/`, `_freeze/`, or `.quarto/`.
- `README.md` is generated; edits should go to `README.Rmd`.
- Bibliography entries in `references.bib` are well-formed BibTeX.
4. **CI / template impact**
- Workflow changes don't break the `copilot-setup-steps.yml`
contract or the publish/preview pipelines.
- Spell-check / link-check failures are fixed at the source
(wordlist or content) rather than suppressed.
Use inline comments for line-specific feedback. Skip generic
praise — be concrete. If the PR looks clean, say so briefly.
# `gh pr comment` is narrowed to `create` so the reviewer can post
# a top-level summary but can't edit or delete prior review
# comments (each run leaves a fresh comment; history stays).
claude_args: |
--allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr view:*),Bash(gh pr diff:*),Bash(gh pr comment create:*)"