oinume journal

Scratchpad of what I learned

Claude Code GitHub Actionsでモデルを指定する

Claude Code GitHub Actionsを使っていて、自分は以下のような2種類のタスクを依頼することが多い。

  • 実装計画を立ててもらう
  • 実装をしてもらう

Claude Codeであれば実装計画はOpusを使って、実装タスクはSonnetを使うみたいな使い分けが簡単にできるのにClaude Code GitHub Actionsではデフォルトだとそれができなかったのでちょっとやってみた。

具体的には、以下のようなclaude.ymlのWorkflowファイルを追加して、GitHub Issueなどのコメントで @claude このタスクの実装計画を Opus で立てて とコメント内にopus, sonnet, haiku のキーワードを含めることで指定したモデルでClaudeが動くようになる。

やっていることは単純で、メッセージ内にopusなどのキーワードをgrepで引っ掛けてmodelを決めているだけ。注意点としてはgrep '\bopus\b'で検索しているため、「このタスクをopusでお願い」ではマッチしないようになっている。かわりに「このタスクを opus でお願い」のようにopusの前後にスペースを入れる必要がある。

そんなわけでClaude Code GitHub Actionsからもモデルが指定できるようになりタスクが捗るようになった(気がする)

claude.yml

name: claude-code
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')))
    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:
      - uses: actions/checkout@v5
        with:
          fetch-depth: 1

      # Extract model from comment/issue body
      - name: Determine Claude Model
        id: determine-claude-model
        run: |
          # Get the comment/issue body based on event type
          if [ "${{ github.event_name }}" = "issue_comment" ]; then
            BODY=$(cat <<'EOF'
          ${{ github.event.comment.body }}
          EOF
          )
          elif [ "${{ github.event_name }}" = "pull_request_review_comment" ]; then
            BODY=$(cat <<'EOF'
          ${{ github.event.comment.body }}
          EOF
          )
          elif [ "${{ github.event_name }}" = "pull_request_review" ]; then
            BODY=$(cat <<'EOF'
          ${{ github.event.review.body }}
          EOF
          )
          elif [ "${{ github.event_name }}" = "issues" ]; then
            BODY=$(cat <<'EOF'
          ${{ github.event.issue.body }}
          EOF
          )
          fi
          
          # Convert to lowercase for case-insensitive matching
          BODY_LOWER=$(echo "$BODY" | tr '[:upper:]' '[:lower:]')

          # Determine model based on keywords
          if echo "$BODY_LOWER" | grep -q '\bopus\b'; then
            MODEL="claude-opus-4-1"
          elif echo "$BODY_LOWER" | grep -q '\bhaiku\b'; then
            MODEL="claude-haiku-4-5"
          else
            # Default to sonnet (including when 'sonnet' keyword is present)
            MODEL="claude-sonnet-4-5"
          fi

          echo "model=$MODEL" >> $GITHUB_OUTPUT
          echo "Selected model: $MODEL"

      - name: Run Claude Code
        id: run-claude-code
        uses: anthropics/claude-code-action@v1
        with:
          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}

          # This is an optional setting that allows Claude to read CI results on PRs
          additional_permissions: |
            actions: read

          allowed_bots: "claude-bot,claude"

          claude_args: |
            --model ${{ steps.determine-claude-model.outputs.model }}
            --allowedTools "Agent"
           (必要に応じてMCPなどを設定)