name: Claude Code Review on: pull_request: types: [opened, synchronize, ready_for_review, reopened] jobs: claude-review: if: ${{ !github.event.pull_request.draft }} runs-on: ubuntu-latest permissions: contents: read pull-requests: write issues: write id-token: write steps: - name: Checkout repository uses: actions/checkout@v7 with: fetch-depth: 0 - name: Run Claude Code Review id: claude-review uses: anthropics/claude-code-action@v1 with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} allowed_bots: 'dependabot[bot],goodolclint-claude[bot],goodolclint-codex[bot]' track_progress: true prompt: | REPO: ${{ github.repository }} PR NUMBER: ${{ github.event.pull_request.number }} Review this pull request for the PSProxmoxVE PowerShell module. Focus areas: 1. **DECISIONS.md compliance** — Check against the architectural decisions (D001-D016). Any violation is a regression. 2. **Code quality** — Cmdlet conventions (sealed, OutputType, ConfirmImpact.High for destructive, VmId ValidateRange), SecureString for passwords, Uri.EscapeDataString on path params, no bare catch blocks, Newtonsoft-only JSON. 3. **API correctness** — Parameter names and enum values must match the PVE OpenAPI spec (see tests/PSProxmoxVE.Core.Tests/Fixtures/ pve-api-enums.pve*.json for valid values per PVE version). 4. **Tests** — New cmdlets should have xUnit service tests and Pester parameter-validation tests. 5. **Security** — No hardcoded credentials, no secrets in logs, TLS verification on by default. Use inline comments for issues tied to specific lines. Skip nitpicks unless they indicate a real problem. You MUST end the review by submitting a formal review verdict with `gh pr review`, because branch protection only recognises a review state — plain PR comments and inline-only comments do not count: - Nothing blocks merge: `gh pr review ${{ github.event.pull_request.number }} --approve --body ""` - Something must change before merge: `gh pr review ${{ github.event.pull_request.number }} --request-changes --body ""` Always pass a non-empty `--body`. If you cannot complete the review for any reason, still submit `gh pr review ${{ github.event.pull_request.number }} --comment --body ""` rather than staying silent. claude_args: | --allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr review:*),Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh api:*)" # claude-code-action self-skips (exit 0, step outcome "success") when the # PR's copy of THIS workflow file differs from the default branch — its # anti-tamper gate, keyed on this one file, so PRs changing ci.yml or # publish.yml are reviewed normally. No review runs, so the verify step # below is skipped and the job would otherwise go green unreviewed. # `execution_file` is only set once Claude actually ran, so an empty # value is the reliable "did not run" signal; `outcome` is not. It is # cause-agnostic: it also stays empty when the action dies before Claude # starts (token exchange, write permission, install failure), which is # why the message points at the step log rather than asserting one cause. - name: Fail closed — review did not run if: always() && steps.claude-review.outputs.execution_file == '' run: | echo "::error title=Claude review did NOT run::No automated review was performed, so this check cannot pass. Most likely the anti-tamper gate - this PR's copy of .github/workflows/claude-code-review.yml differs from the default branch. Only THIS file trips it; PRs changing other workflows are reviewed normally. This step also fires on any failure before Claude starts (expired token, missing write permission, install failure), so read the 'Run Claude Code Review' step log to tell them apart. Either way this PR requires operator review before merge." exit 1 - name: Verify formal review was submitted if: always() && steps.claude-review.outputs.execution_file != '' env: GH_TOKEN: ${{ github.token }} PR_NUMBER: ${{ github.event.pull_request.number }} REPO: ${{ github.repository }} run: | # Branch protection requires a formal review state. Plain PR comments # don't count, and inline-only comments create empty-body COMMENTED # reviews that don't count either. formal_count=$(gh api "repos/$REPO/pulls/$PR_NUMBER/reviews" \ --jq '[.[] | select(.user.login == "claude[bot]") | select(.state == "APPROVED" or .state == "CHANGES_REQUESTED" or ((.body // "") | length > 0))] | length') echo "Formal claude[bot] reviews on PR #$PR_NUMBER: $formal_count" if [ "$formal_count" -eq 0 ]; then echo "::error title=No formal review submitted::Claude reviewed the PR but did not call 'gh pr review'. Re-run the workflow or invoke '@claude re-review' on the PR." exit 1 fi