From 349d875616635061c90743243709a2a93b4427e9 Mon Sep 17 00:00:00 2001 From: HAOCHENYE <21724054@zju.edu.cn> Date: Sun, 1 Mar 2026 06:51:29 +0000 Subject: [PATCH] [CI] Refactor claude-code CI workflow --- .github/edit-issue-labels.sh | 88 +++++++++++++++++ .github/gh.sh | 85 ++++++++++++++++ .github/workflows/claude-code-review.yml | 96 ------------------- .github/workflows/claude-general.yml | 48 ++++++++++ .github/workflows/claude-issue-label.yaml | 43 +++++++++ .../{claude.yml => claude-review.yml} | 32 ++----- 6 files changed, 270 insertions(+), 122 deletions(-) create mode 100755 .github/edit-issue-labels.sh create mode 100755 .github/gh.sh delete mode 100644 .github/workflows/claude-code-review.yml create mode 100644 .github/workflows/claude-general.yml create mode 100644 .github/workflows/claude-issue-label.yaml rename .github/workflows/{claude.yml => claude-review.yml} (70%) diff --git a/.github/edit-issue-labels.sh b/.github/edit-issue-labels.sh new file mode 100755 index 000000000..1c9adb36b --- /dev/null +++ b/.github/edit-issue-labels.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash +# +# Edits labels on a GitHub issue. +# Usage: ./scripts/edit-issue-labels.sh --issue 123 --add-label bug --add-label needs-triage --remove-label untriaged +# + +set -euo pipefail + +ISSUE="" +ADD_LABELS=() +REMOVE_LABELS=() + +# Parse arguments +while [[ $# -gt 0 ]]; do + case $1 in + --issue) + ISSUE="$2" + shift 2 + ;; + --add-label) + ADD_LABELS+=("$2") + shift 2 + ;; + --remove-label) + REMOVE_LABELS+=("$2") + shift 2 + ;; + *) + exit 1 + ;; + esac +done + +# Validate issue number +if [[ -z "$ISSUE" ]]; then + exit 1 +fi + +if ! [[ "$ISSUE" =~ ^[0-9]+$ ]]; then + exit 1 +fi + +if [[ ${#ADD_LABELS[@]} -eq 0 && ${#REMOVE_LABELS[@]} -eq 0 ]]; then + exit 1 +fi + +# Fetch valid labels from the repo +VALID_LABELS=$(gh label list --limit 500 --json name --jq '.[].name') + +# Filter to only labels that exist in the repo +FILTERED_ADD=() +for label in "${ADD_LABELS[@]}"; do + if echo "$VALID_LABELS" | grep -qxF "$label"; then + FILTERED_ADD+=("$label") + fi +done + +FILTERED_REMOVE=() +for label in "${REMOVE_LABELS[@]}"; do + if echo "$VALID_LABELS" | grep -qxF "$label"; then + FILTERED_REMOVE+=("$label") + fi +done + +if [[ ${#FILTERED_ADD[@]} -eq 0 && ${#FILTERED_REMOVE[@]} -eq 0 ]]; then + exit 0 +fi + +# Build gh command arguments +GH_ARGS=("issue" "edit" "$ISSUE") + +for label in "${FILTERED_ADD[@]}"; do + GH_ARGS+=("--add-label" "$label") +done + +for label in "${FILTERED_REMOVE[@]}"; do + GH_ARGS+=("--remove-label" "$label") +done + +gh "${GH_ARGS[@]}" + +if [[ ${#FILTERED_ADD[@]} -gt 0 ]]; then + echo "Added: ${FILTERED_ADD[*]}" +fi +if [[ ${#FILTERED_REMOVE[@]} -gt 0 ]]; then + echo "Removed: ${FILTERED_REMOVE[*]}" +fi + diff --git a/.github/gh.sh b/.github/gh.sh new file mode 100755 index 000000000..6113a75be --- /dev/null +++ b/.github/gh.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Wrapper around gh CLI that only allows specific subcommands and flags. +# All commands are scoped to the current repository via GH_REPO or GITHUB_REPOSITORY. +# +# Usage: +# ./scripts/gh.sh issue view 123 +# ./scripts/gh.sh issue view 123 --comments +# ./scripts/gh.sh issue list --state open --limit 20 +# ./scripts/gh.sh search issues "search query" --limit 10 +# ./scripts/gh.sh label list --limit 100 + +ALLOWED_FLAGS=(--comments --state --limit --label) +FLAGS_WITH_VALUES=(--state --limit --label) + +SUB1="${1:-}" +SUB2="${2:-}" +CMD="$SUB1 $SUB2" +case "$CMD" in + "issue view"|"issue list"|"search issues"|"label list") + ;; + *) + exit 1 + ;; +esac + +shift 2 + +# Separate flags from positional arguments +POSITIONAL=() +FLAGS=() +skip_next=false +for arg in "$@"; do + if [[ "$skip_next" == true ]]; then + FLAGS+=("$arg") + skip_next=false + elif [[ "$arg" == -* ]]; then + flag="${arg%%=*}" + matched=false + for allowed in "${ALLOWED_FLAGS[@]}"; do + if [[ "$flag" == "$allowed" ]]; then + matched=true + break + fi + done + if [[ "$matched" == false ]]; then + exit 1 + fi + FLAGS+=("$arg") + # If flag expects a value and isn't using = syntax, skip next arg + if [[ "$arg" != *=* ]]; then + for vflag in "${FLAGS_WITH_VALUES[@]}"; do + if [[ "$flag" == "$vflag" ]]; then + skip_next=true + break + fi + done + fi + else + POSITIONAL+=("$arg") + fi +done + +REPO="${GH_REPO:-${GITHUB_REPOSITORY:-}}" + +if [[ "$CMD" == "search issues" ]]; then + if [[ -z "$REPO" ]]; then + exit 1 + fi + QUERY="${POSITIONAL[0]:-}" + QUERY_LOWER=$(echo "$QUERY" | tr '[:upper:]' '[:lower:]') + if [[ "$QUERY_LOWER" == *"repo:"* || "$QUERY_LOWER" == *"org:"* || "$QUERY_LOWER" == *"user:"* ]]; then + exit 1 + fi + gh "$SUB1" "$SUB2" "$QUERY" --repo "$REPO" "${FLAGS[@]}" +else + # Reject URLs in positional args to prevent cross-repo access + for pos in "${POSITIONAL[@]}"; do + if [[ "$pos" == http://* || "$pos" == https://* ]]; then + exit 1 + fi + done + gh "$SUB1" "$SUB2" "${POSITIONAL[@]}" "${FLAGS[@]}" +fi diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml deleted file mode 100644 index e7ec03271..000000000 --- a/.github/workflows/claude-code-review.yml +++ /dev/null @@ -1,96 +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" -# -# 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: read -# 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 }} -# # Prompt A workaround for claude code action bug of `Fork` PR -# prompt: | -# REPO: ${{ github.repository }} -# PR NUMBER: ${{ github.event.pull_request.number }} -# -# Please review this pull request. -# -# You can use multiple subagents to parallelize tasks. Each subagent should be told the PR title and description. -# -# Use `gh pr view --comments` and `gh pr diff --patch` to access the PR content. -# You are in repository without that patch applied. Do not apply it. -# Assume the patch will be build and tested by other GitHub Actions workflows. -# -# Focus on: -# - Code quality, style, and best practices -# - Potential bugs, issues, incorrect logic -# - Security implications -# - CLAUDE.md compliance -# -# For every issue, rate how significant it is. Make sure you are confident in your diagnosis. -# -# For every CLAUDE.md compliance violation, check if repo has other places where this issue appears. -# If it does, do not point out the violation in the place it appears. -# Instead in the final top-level comment, include a suggestion how to improve CLAUDE.md. -# -# Provide detailed feedback as PR comments, using inline comments for specific issues. -# Use `gh pr comment ${{ github.event.pull_request.number }} --body "..."` for top-level summary. Keep it short. -# Use `mcp__github_inline_comment__create_inline_comment` to highlight specific code issues. -# Prefix all your GitHub comments with "Claude: ". -# Use `gh pr view --comments` output to make sure you don't comment about the same issue twice. -# -# When linking to code in inline comments, follow the following format precisely, otherwise the Markdown preview -# won't render correctly: https://github.com/${{ github.repository }}/blob/${{ github.event.pull_request.head.sha }}/README.md#L10-L15 -# claude_args: | -# --max-turns 50 -# --model claude-opus-4-6 -# --allowedTools " -# Read,Write,Edit,MultiEdit,LS,Grep,Glob, -# Bash(cat:*),Bash(test:*),Bash(printf:*),Bash(jq:*),Bash(head:*),Bash(git:*),Bash(gh:*), -# mcp__github_inline_comment__create_inline_comment, -# " -# -# # Run even when triggered by 3rd party developer's PR -# allowed_non_write_users: "*" -# -# # This requires "tag mode", which is currently bugged: -# # https://github.com/anthropics/claude-code-action/issues/939 -# track_progress: false -# -# # Enable access to other GH Actions outputs -# additional_permissions: | -# actions: read -# # 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-general.yml b/.github/workflows/claude-general.yml new file mode 100644 index 000000000..4ebb61964 --- /dev/null +++ b/.github/workflows/claude-general.yml @@ -0,0 +1,48 @@ +name: Claude Code General + +on: + issue_comment: + types: [created] + pull_request_review_comment: + types: [created] + issues: + types: [opened, assigned] + pull_request_review: + types: [submitted] + +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') && !contains(github.event.comment.body, '@claude review')) || + (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: write + pull-requests: write + issues: write + id-token: write + actions: read + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Run Claude Code + id: claude + uses: anthropics/claude-code-action@v1 + with: + claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + claude_args: | + --max-turns 50 + --model claude-opus-4-6 + + # Run even when triggered by 3rd party developer's PR + allowed_non_write_users: "*" + + # This requires "tag mode", which is currently bugged: + # https://github.com/anthropics/claude-code-action/issues/939 + track_progress: false + diff --git a/.github/workflows/claude-issue-label.yaml b/.github/workflows/claude-issue-label.yaml new file mode 100644 index 000000000..94184b649 --- /dev/null +++ b/.github/workflows/claude-issue-label.yaml @@ -0,0 +1,43 @@ +name: Issue Triage +on: + issues: + types: [opened] + +jobs: + triage: + runs-on: ubuntu-latest + permissions: + issues: write + id-token: write + steps: + - uses: actions/checkout@v4 + - uses: anthropics/claude-code-action@v1 + with: + claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + prompt: | + REPO: ${{ github.repository }} + ISSUE NUMBER: ${{ github.event.issue.number }} + TITLE: ${{ github.event.issue.title }} + BODY: ${{ github.event.issue.body }} + AUTHOR: ${{ github.event.issue.user.login }} + + Analyze this new issue and: + 1. Determine if it's a bug report, feature request, or question + 2. Assess priority (critical, high, medium, low) + 3. Suggest appropriate labels + 4. Check if it duplicates existing issues + + Use ./.github/gh.sh to interact with GitHub: + - `./.github/gh.sh issue view [number]` to view the issue + - `./.github/gh.sh search issues "query"` to find similar issues + - `./.github/gh.sh label list` to see available labels + + Based on your analysis, add the appropriate labels using: + `./.github/edit-issue-labels.sh --issue [number] --add-label "label1" --add-label "label2"` + + If it appears to be a duplicate, post a comment mentioning the original issue. + + claude_args: | + --allowedTools "Bash(./.github/gh.sh:*),Bash(./.github/edit-issue-labels.sh:*)" + + diff --git a/.github/workflows/claude.yml b/.github/workflows/claude-review.yml similarity index 70% rename from .github/workflows/claude.yml rename to .github/workflows/claude-review.yml index 78b32d615..57b32dfea 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude-review.yml @@ -1,22 +1,12 @@ -name: Claude Code +name: Claude Code Review on: - issue_comment: - types: [created] pull_request_review_comment: types: [created] - issues: - types: [opened, assigned] - pull_request_review: - types: [submitted] 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'))) + claude-review: + if: contains(github.event.comment.body, '@claude review') runs-on: ubuntu-latest permissions: contents: read @@ -30,8 +20,8 @@ jobs: with: fetch-depth: 1 - - name: Run Claude Code - id: claude + - name: Run Claude Code Review + id: claude-review uses: anthropics/claude-code-action@v1 with: claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} @@ -65,7 +55,7 @@ jobs: When linking to code in inline comments, follow the following format precisely, otherwise the Markdown preview won't render correctly: https://github.com/${{ github.repository }}/blob/${{ github.event.pull_request.head.sha }}/README.md#L10-L15 claude_args: | - --max-turns 50 + --max-turns 30 --model claude-opus-4-6 --allowedTools " Read,Write,Edit,MultiEdit,LS,Grep,Glob, @@ -80,13 +70,3 @@ jobs: # https://github.com/anthropics/claude-code-action/issues/939 track_progress: false - # Enable access to other GH Actions outputs - additional_permissions: | - actions: read - # 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 - -