From 3c9dddc17ace1072e9cc1ea7388e53f291ac0f3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?JasonXuDeveloper=20-=20=E5=82=91?= Date: Wed, 28 Jan 2026 09:41:24 +1100 Subject: [PATCH 1/5] feat(ci): migrate from Claude to GitHub Copilot code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove claude-code-review.yml and claude.yml workflows - Update auto-approve.yml to check Copilot inline comments instead of Claude - Copilot code review is now configured via repository ruleset This change reduces CI costs and avoids potential account suspension from running Claude ~100 times/day across different GitHub runners. Co-Authored-By: Claude Opus 4.5 Signed-off-by: JasonXuDeveloper - 傑 --- .github/workflows/auto-approve.yml | 29 ++++++------- .github/workflows/claude-code-review.yml | 49 ---------------------- .github/workflows/claude.yml | 52 ------------------------ 3 files changed, 15 insertions(+), 115 deletions(-) delete mode 100644 .github/workflows/claude-code-review.yml delete mode 100644 .github/workflows/claude.yml diff --git a/.github/workflows/auto-approve.yml b/.github/workflows/auto-approve.yml index 4ef49be6..661635a4 100644 --- a/.github/workflows/auto-approve.yml +++ b/.github/workflows/auto-approve.yml @@ -6,7 +6,7 @@ on: types: [completed] # Also trigger on workflow run completion for reusable workflows workflow_run: - workflows: ["PR Tests", "Claude Code Review"] + workflows: ["PR Tests"] types: [completed] permissions: read-all @@ -50,18 +50,19 @@ jobs: HEAD_SHA=$(gh api repos/${{ github.repository }}/pulls/$PR_NUMBER --jq '.head.sha') echo "Head SHA: $HEAD_SHA" - # Check Claude review - look for "No issues found" comment from claude[bot] - # Claude posts a PR comment with this message when review passes - CLAUDE_COMMENTS=$(gh api "repos/${{ github.repository }}/issues/$PR_NUMBER/comments" --jq '[.[] | select(.user.login == "claude[bot]") | .body] | join("\n")') + # Check Copilot review - Copilot leaves "Comment" reviews, not "Approve" + # If Copilot left inline comments, it found issues to address + # No comments = no issues found + COPILOT_COMMENTS=$(gh api "repos/${{ github.repository }}/pulls/$PR_NUMBER/comments" --jq '[.[] | select(.user.login == "copilot[bot]")] | length') - CLAUDE_APPROVED="false" - if echo "$CLAUDE_COMMENTS" | grep -q "No issues found. Checked for bugs and CLAUDE.md compliance"; then - echo "Claude review: No issues found" - CLAUDE_APPROVED="true" + COPILOT_OK="false" + if [ "$COPILOT_COMMENTS" -eq 0 ]; then + echo "Copilot review: No issues found (0 inline comments)" + COPILOT_OK="true" else - echo "Claude review: Issues found or not yet complete" + echo "Copilot review: Found $COPILOT_COMMENTS inline comments - issues need addressing" fi - echo "Claude approved: $CLAUDE_APPROVED" + echo "Copilot OK: $COPILOT_OK" # Check Unity Tests status (commit status, not check run) UNITY_STATUS=$(gh api repos/${{ github.repository }}/commits/$HEAD_SHA/status --jq '.statuses[] | select(.context == "Unity Tests") | .state' | head -1) @@ -77,11 +78,11 @@ jobs: fi # Determine if we should approve - if [ "$CLAUDE_APPROVED" == "true" ] && [ "$UNITY_STATUS" == "success" ]; then - echo "All required checks passed and Claude found no issues!" + if [ "$COPILOT_OK" == "true" ] && [ "$UNITY_STATUS" == "success" ]; then + echo "All required checks passed and Copilot found no issues!" echo "should_approve=true" >> $GITHUB_OUTPUT else - echo "Required checks not yet passed or Claude found issues" + echo "Required checks not yet passed or Copilot found issues" echo "should_approve=false" >> $GITHUB_OUTPUT fi @@ -111,6 +112,6 @@ jobs: run: | PR_NUMBER="${{ steps.pr.outputs.number }}" - gh pr review $PR_NUMBER -R ${{ github.repository }} --approve --body "Auto-approved: Claude review found no issues and Unity Tests passed (or were skipped for non-code changes)." + gh pr review $PR_NUMBER -R ${{ github.repository }} --approve --body "Auto-approved: Copilot review found no issues and Unity Tests passed (or were skipped for non-code changes)." echo "PR #$PR_NUMBER approved!" diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml deleted file mode 100644 index 4d3e3a80..00000000 --- a/.github/workflows/claude-code-review.yml +++ /dev/null @@ -1,49 +0,0 @@ -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" - -permissions: - contents: read - pull-requests: write - issues: read - -jobs: - claude-review: - # Optional: Filter by PR author - # if: | - # github.event.pull_request.user.login == 'external-contributor' || - # github.event.pull_request.user.login == 'new-developer' || - # github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR' - - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - issues: read - id-token: write - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - name: Run Claude Code Review - id: claude-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' - plugins: 'code-review@claude-code-plugins' - prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}' - # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md - # or https://code.claude.com/docs/en/cli-reference for available options - diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml deleted file mode 100644 index 6e3dd3be..00000000 --- a/.github/workflows/claude.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: Claude Code - -on: - issue_comment: - types: [created] - pull_request_review_comment: - types: [created] - issues: - types: [opened, assigned] - pull_request_review: - types: [submitted] - -permissions: read-all - -jobs: - claude: - if: | - (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || - (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: read - issues: read - id-token: write - actions: read # Required for Claude to read CI results on PRs - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - name: Run Claude Code - id: claude - uses: anthropics/claude-code-action@v1 - with: - claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - - # This is an optional setting that allows Claude to read CI results on PRs - additional_permissions: | - actions: read - - # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it. - # prompt: 'Update the pull request description to include a summary of changes.' - - # Optional: Add claude_args to customize behavior and configuration - # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md - # or https://code.claude.com/docs/en/cli-reference for available options - # claude_args: '--allowed-tools Bash(gh pr:*)' - From bd46b58cdd82962cd2a23551cd9901b87af57265 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?JasonXuDeveloper=20-=20=E5=82=91?= Date: Wed, 28 Jan 2026 09:48:05 +1100 Subject: [PATCH 2/5] fix(ci): address Copilot review feedback and skip tests for CI-only changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Check Copilot review existence before checking comments (fixes race condition) - Only trigger Unity tests for unity-tests.yml and pr-tests.yml changes - Other CI workflow changes (auto-approve, etc.) no longer run Unity tests Co-Authored-By: Claude Opus 4.5 Signed-off-by: JasonXuDeveloper - 傑 --- .github/workflows/auto-approve.yml | 13 ++++++++----- .github/workflows/pr-tests.yml | 3 ++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/auto-approve.yml b/.github/workflows/auto-approve.yml index 661635a4..fd6ed2b4 100644 --- a/.github/workflows/auto-approve.yml +++ b/.github/workflows/auto-approve.yml @@ -50,14 +50,17 @@ jobs: HEAD_SHA=$(gh api repos/${{ github.repository }}/pulls/$PR_NUMBER --jq '.head.sha') echo "Head SHA: $HEAD_SHA" - # Check Copilot review - Copilot leaves "Comment" reviews, not "Approve" - # If Copilot left inline comments, it found issues to address - # No comments = no issues found + # Check Copilot review - must verify Copilot has actually reviewed first + # Copilot leaves "COMMENTED" reviews (never "APPROVED" or "CHANGES_REQUESTED") + # We need to: 1) Confirm a review exists, 2) Check for inline comments + COPILOT_REVIEW=$(gh api "repos/${{ github.repository }}/pulls/$PR_NUMBER/reviews" --jq '[.[] | select(.user.login == "copilot[bot]")] | length') COPILOT_COMMENTS=$(gh api "repos/${{ github.repository }}/pulls/$PR_NUMBER/comments" --jq '[.[] | select(.user.login == "copilot[bot]")] | length') COPILOT_OK="false" - if [ "$COPILOT_COMMENTS" -eq 0 ]; then - echo "Copilot review: No issues found (0 inline comments)" + if [ "$COPILOT_REVIEW" -eq 0 ]; then + echo "Copilot review: Not yet reviewed (no review found)" + elif [ "$COPILOT_COMMENTS" -eq 0 ]; then + echo "Copilot review: Reviewed with no issues (0 inline comments)" COPILOT_OK="true" else echo "Copilot review: Found $COPILOT_COMMENTS inline comments - issues need addressing" diff --git a/.github/workflows/pr-tests.yml b/.github/workflows/pr-tests.yml index 65ceaa9d..53d10dd8 100644 --- a/.github/workflows/pr-tests.yml +++ b/.github/workflows/pr-tests.yml @@ -38,7 +38,8 @@ jobs: - 'UnityProject/Packages/com.jasonxudeveloper.jengine.core/**' - 'UnityProject/Packages/com.jasonxudeveloper.jengine.util/**' - 'UnityProject/Assets/Tests/**' - - '.github/workflows/**' + - '.github/workflows/unity-tests.yml' + - '.github/workflows/pr-tests.yml' run-tests: name: Run Unity Tests From 7f9a3e1ca7d7d906c9e2eebd09dc27f7dd48023e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?JasonXuDeveloper=20-=20=E5=82=91?= Date: Wed, 28 Jan 2026 09:56:10 +1100 Subject: [PATCH 3/5] fix(ci): use correct Copilot bot usernames MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Reviews: copilot-pull-request-reviewer[bot] - Comments: Copilot Co-Authored-By: Claude Opus 4.5 Signed-off-by: JasonXuDeveloper - 傑 --- .github/workflows/auto-approve.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/auto-approve.yml b/.github/workflows/auto-approve.yml index fd6ed2b4..8bf31b2e 100644 --- a/.github/workflows/auto-approve.yml +++ b/.github/workflows/auto-approve.yml @@ -53,8 +53,9 @@ jobs: # Check Copilot review - must verify Copilot has actually reviewed first # Copilot leaves "COMMENTED" reviews (never "APPROVED" or "CHANGES_REQUESTED") # We need to: 1) Confirm a review exists, 2) Check for inline comments - COPILOT_REVIEW=$(gh api "repos/${{ github.repository }}/pulls/$PR_NUMBER/reviews" --jq '[.[] | select(.user.login == "copilot[bot]")] | length') - COPILOT_COMMENTS=$(gh api "repos/${{ github.repository }}/pulls/$PR_NUMBER/comments" --jq '[.[] | select(.user.login == "copilot[bot]")] | length') + # Note: Copilot uses different usernames for reviews vs comments + COPILOT_REVIEW=$(gh api "repos/${{ github.repository }}/pulls/$PR_NUMBER/reviews" --jq '[.[] | select(.user.login == "copilot-pull-request-reviewer[bot]")] | length') + COPILOT_COMMENTS=$(gh api "repos/${{ github.repository }}/pulls/$PR_NUMBER/comments" --jq '[.[] | select(.user.login == "Copilot")] | length') COPILOT_OK="false" if [ "$COPILOT_REVIEW" -eq 0 ]; then From 9fe53fa30edc44d7eccb354e548725487722ffb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?JasonXuDeveloper=20-=20=E5=82=91?= Date: Wed, 28 Jan 2026 09:56:49 +1100 Subject: [PATCH 4/5] fix(ci): check unresolved Copilot threads instead of all comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolved threads should not block auto-approve. Use GraphQL to count only unresolved review threads from Copilot. Co-Authored-By: Claude Opus 4.5 Signed-off-by: JasonXuDeveloper - 傑 --- .github/workflows/auto-approve.yml | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/.github/workflows/auto-approve.yml b/.github/workflows/auto-approve.yml index 8bf31b2e..d11cbddf 100644 --- a/.github/workflows/auto-approve.yml +++ b/.github/workflows/auto-approve.yml @@ -52,19 +52,36 @@ jobs: # Check Copilot review - must verify Copilot has actually reviewed first # Copilot leaves "COMMENTED" reviews (never "APPROVED" or "CHANGES_REQUESTED") - # We need to: 1) Confirm a review exists, 2) Check for inline comments - # Note: Copilot uses different usernames for reviews vs comments + # We need to: 1) Confirm a review exists, 2) Check for UNRESOLVED comments + # Note: Copilot uses "copilot-pull-request-reviewer[bot]" for reviews COPILOT_REVIEW=$(gh api "repos/${{ github.repository }}/pulls/$PR_NUMBER/reviews" --jq '[.[] | select(.user.login == "copilot-pull-request-reviewer[bot]")] | length') - COPILOT_COMMENTS=$(gh api "repos/${{ github.repository }}/pulls/$PR_NUMBER/comments" --jq '[.[] | select(.user.login == "Copilot")] | length') + + # Use GraphQL to count unresolved Copilot review threads + UNRESOLVED_THREADS=$(gh api graphql -f query=' + query($owner: String!, $repo: String!, $pr: Int!) { + repository(owner: $owner, name: $repo) { + pullRequest(number: $pr) { + reviewThreads(first: 100) { + nodes { + isResolved + comments(first: 1) { + nodes { author { login } } + } + } + } + } + } + }' -f owner="${{ github.repository_owner }}" -f repo="${{ github.event.repository.name }}" -F pr="$PR_NUMBER" \ + --jq '[.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false and .comments.nodes[0].author.login == "copilot-pull-request-reviewer")] | length') COPILOT_OK="false" if [ "$COPILOT_REVIEW" -eq 0 ]; then echo "Copilot review: Not yet reviewed (no review found)" - elif [ "$COPILOT_COMMENTS" -eq 0 ]; then - echo "Copilot review: Reviewed with no issues (0 inline comments)" + elif [ "$UNRESOLVED_THREADS" -eq 0 ]; then + echo "Copilot review: Reviewed with no unresolved issues" COPILOT_OK="true" else - echo "Copilot review: Found $COPILOT_COMMENTS inline comments - issues need addressing" + echo "Copilot review: Found $UNRESOLVED_THREADS unresolved threads - issues need addressing" fi echo "Copilot OK: $COPILOT_OK" From 51995a3fb7a1eebce63cab6b0a9d4e1e45834508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?JasonXuDeveloper=20-=20=E5=82=91?= Date: Wed, 28 Jan 2026 09:57:26 +1100 Subject: [PATCH 5/5] fix(ci): simplify GraphQL query to avoid variable escaping issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.5 Signed-off-by: JasonXuDeveloper - 傑 --- .github/workflows/auto-approve.yml | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/.github/workflows/auto-approve.yml b/.github/workflows/auto-approve.yml index d11cbddf..5c60a00b 100644 --- a/.github/workflows/auto-approve.yml +++ b/.github/workflows/auto-approve.yml @@ -57,21 +57,10 @@ jobs: COPILOT_REVIEW=$(gh api "repos/${{ github.repository }}/pulls/$PR_NUMBER/reviews" --jq '[.[] | select(.user.login == "copilot-pull-request-reviewer[bot]")] | length') # Use GraphQL to count unresolved Copilot review threads - UNRESOLVED_THREADS=$(gh api graphql -f query=' - query($owner: String!, $repo: String!, $pr: Int!) { - repository(owner: $owner, name: $repo) { - pullRequest(number: $pr) { - reviewThreads(first: 100) { - nodes { - isResolved - comments(first: 1) { - nodes { author { login } } - } - } - } - } - } - }' -f owner="${{ github.repository_owner }}" -f repo="${{ github.event.repository.name }}" -F pr="$PR_NUMBER" \ + REPO_OWNER="${{ github.repository_owner }}" + REPO_NAME="${{ github.repository }}" + REPO_NAME="${REPO_NAME#*/}" # Extract repo name from owner/repo + UNRESOLVED_THREADS=$(gh api graphql -f query="query { repository(owner: \"$REPO_OWNER\", name: \"$REPO_NAME\") { pullRequest(number: $PR_NUMBER) { reviewThreads(first: 100) { nodes { isResolved comments(first: 1) { nodes { author { login } } } } } } } }" \ --jq '[.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false and .comments.nodes[0].author.login == "copilot-pull-request-reviewer")] | length') COPILOT_OK="false"