🪕

Claude Code Actions を使って CI 失敗をしつこく自動で直す

に公開

CI失敗自動検知・修正ワークフローの実装

Claude Code Actions (CCA) を使って, CI失敗の検知から修正まで行う自動ワークフローを作ってみました。

  • PRのCI失敗は PR 内で成功するまで CCA に直させます
  • main ブランチにマージした後に失敗が起こる場合は、それを解決するための issue を作成し、そこから 解決用 PR を作らせます
  • ラベルで状態管理します
  • これでもかというくらい、CIが失敗したら CC に依頼して直させます

アーキテクチャ概要

このワークフローは3つのYAMLファイルで構成されています:

  1. ci.yml - メインのCI/CDパイプライン
  2. ci-result-handler.yml - CI結果の監視とラベル管理
  3. ci-fix.yml - CI失敗の自動修正

1. メインCI/CDパイプライン (ci.yml)

name: CI/CD Pipeline

on:
  push:
    branches: [ main, develop, feature/* ]
  pull_request:
    branches: [ main, develop ]

jobs:
  test:
    name: Test Suite
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [18.x, 20.x]

    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      
    - name: Setup Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v4
      with:
        node-version: ${{ matrix.node-version }}
        cache: 'npm'
        
    - name: Install dependencies
      run: npm ci
      
    - name: Run linting
      run: npm run lint
      
    - name: Run unit tests
      run: npm run test

2. CI結果監視ワークフロー (ci-result-handler.yml)

CI結果を監視し、失敗時に自動的にラベルを付与するワークフローです。

主な機能

  • PR用CI結果ハンドラー: PR関連のCI結果を監視
  • Mainブランチ用CI失敗ハンドラー: mainブランチでのCI失敗時にIssueを自動作成
name: CI Result Handler

on:
  workflow_run:
    workflows: ["CI/CD Pipeline"]
    types: [completed]

jobs:
  handle-pr-ci-result:
    name: Handle PR CI Result
    runs-on: ubuntu-latest
    
    if: github.event.workflow_run.event == 'pull_request'
    
    permissions:
      contents: read
      pull-requests: write
      issues: write
      actions: read
      id-token: write
    
    steps:
      - name: Handle CI Result
        uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
          script: |
            # PR番号の取得ロジック
            let prNumber = null;
            
            if (context.payload.workflow_run.pull_requests && 
                context.payload.workflow_run.pull_requests.length > 0) {
              prNumber = context.payload.workflow_run.pull_requests[0].number;
            } else {
              # フォールバック: ブランチ名でPRを検索
              const headBranch = context.payload.workflow_run.head_branch;
              const { data: pullRequests } = await github.rest.pulls.list({
                owner: context.repo.owner,
                repo: context.repo.repo,
                head: `${context.repo.owner}:${headBranch}`,
                state: 'open'
              });
              
              if (pullRequests.length > 0) {
                prNumber = pullRequests[0].number;
              }
            }
            
            const conclusion = context.payload.workflow_run.conclusion;
            
            if (conclusion === 'failure') {
              # CI失敗時: ci-failureラベルを追加
              await github.rest.issues.addLabels({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: prNumber,
                labels: ['ci-failure']
              });
              
            } else if (conclusion === 'success') {
              # CI成功時: ci-passedラベルを追加
              await github.rest.issues.addLabels({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: prNumber,
                labels: ['ci-passed']
              });
            }

Mainブランチ用CI失敗ハンドラー

  handle-main-ci-failure:
    name: Handle Main CI Failure
    runs-on: ubuntu-latest
    
    if: |
      github.event.workflow_run.event == 'push' &&
      github.event.workflow_run.head_branch == 'main' &&
      github.event.workflow_run.conclusion == 'failure'
    
    steps:
      - name: Create Issue for Main CI Failure
        uses: actions/github-script@v7
        with:
          script: |
            const issueTitle = `🚨 Main CI Failure: ${commitMessage}`;
            const issueBody = `## Main Branch CI Failure
            
            **失敗時刻**: ${timestamp}
            **コミット**: \`${commitSha}\` by ${author}
            **ワークフロー**: [実行結果を確認](${runUrl})
            
            @claude この CI 失敗を分析し、修正して, PRを作成してください。`;
            
            await github.rest.issues.create({
              owner: context.repo.owner,
              repo: context.repo.repo,
              title: issueTitle,
              body: issueBody,
              labels: ['ci-failure', 'main-branch', 'bug']
            });

PR自動作成

上記で、CC にPR作成を頼んでも、作成リンクだけつくられてプルリクエストまで作ってくれないことが多いので、PR作成リンクを検知してPRを自動作成する Github Actions も作っています。

name: Create PR from Comment

on:
  issue_comment:
    types: [edited]

jobs:
  create-pr:
    name: Create PR from Comment
    runs-on: ubuntu-latest
    if: contains(github.event.comment.body, 'Create PR')
    
    permissions:
      contents: write
      pull-requests: write
      issues: write
    
    steps:
      - name: Extract and Create PR
        uses: actions/github-script@v7
        with:
          github-token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
          script: |
            // Compare URLからブランチ情報を抽出
            const comment = context.payload.comment.body;
            const urlMatch = comment.match(/https:\/\/github\.com\/[^\/\s]+\/[^\/\s]+\/compare\/[^\s)]+/);
            
            if (!urlMatch) {
              await github.rest.issues.createComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: context.payload.issue.number,
                body: '❌ Create PR link not found'
              });
              return;
            }

            // URLをパースしてbase...head形式を抽出
            const url = new URL(urlMatch[0]);
            const pathMatch = url.pathname.match(/\/compare\/(.+)\.\.\.(.+)/);
            
            if (!pathMatch) {
              throw new Error('Invalid URL format');
            }

            // PR作成
            try {
              const newPR = await github.rest.pulls.create({
                owner: context.repo.owner,
                repo: context.repo.repo,
                title: url.searchParams.get('title') || `PR: ${pathMatch[2]} → ${pathMatch[1]}`,
                body: url.searchParams.get('body') || `Auto-created from Issue #${context.payload.issue.number}`,
                head: pathMatch[2],
                base: pathMatch[1]
              });

              await github.rest.issues.createComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: context.payload.issue.number,
                body: `PR created: ${newPR.data.html_url}`
              });

            } catch (error) {              
              await github.rest.issues.createComment({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: context.payload.issue.number,
                body: `Error: ${error.message}${isInvalidBranch ? '\n\n🔒 Issue closed due to invalid branch.' : ''}`
              });
            }

3. CI失敗自動修正ワークフロー (ci-fix.yml)

ci-failureラベルが付与されたPRに対して、Claude Codeが自動的に修正を行います。

name: Claude CI Fix

on:
  pull_request:
    types: [labeled]

jobs:
  claude-ci-fix:
    name: Claude CI Fix
    runs-on: ubuntu-latest
    
    if: github.event.action == 'labeled' && github.event.label.name == 'ci-failure'
    
    permissions:
      contents: write
      pull-requests: write
      issues: write
      id-token: write
      actions: read
    
    steps:
      - name: Remove ci-failure label
        uses: actions/github-script@v7
        with:
          script: |
            await github.rest.issues.removeLabel({
              owner: context.repo.owner,
              repo: context.repo.repo,
              issue_number: context.payload.pull_request.number,
              name: 'ci-failure'
            });
            
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
          fetch-depth: 1

      - name: Run Claude Code for CI Fix
        uses: anthropics/claude-code-action@beta
        with:
          claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
          github_token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
          
          additional_permissions: |
            actions: read
          
          direct_prompt: |
            CIが失敗しました。PR #${{ github.event.pull_request.number }} の CI 失敗を修正してください:
            
            - ワークフロー結果を確認し、失敗の原因を分析してください
            - 必要な修正を行ってください  
            - 修正後は適切なテストを実行してください
            - 修正が完了したらコミットしてください

Claude Codeトリガーワークフロー (claude.yml)

@claudeメンションでClaude Codeをトリガーできるワークフローも用意します。
こちらは、公式の例
https://github.com/anthropics/claude-code-action/blob/main/examples/claude.yml
を少し改造したものです。
これを bot から起動するためには、bot 自体に PAT トークンを渡す必要があります。つまり、botにユーザとして振る舞わせるということですね。

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: write
      pull-requests: write
      issues: write
      id-token: write
      actions: read
    
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 1

      - name: Run Claude Code
        uses: anthropics/claude-code-action@beta
        with:
          claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
          timeout_minutes: "60"
          additional_permissions: |
            actions: read
          allowed_tools: "Agent,Bash,Edit,MultiEdit,WebFetch,WebSearch,Write"

ワークフローの動作

  1. PRを作成/更新
  2. CI/CDパイプラインが実行
  3. CI結果監視ワークフローが結果を検知
  4. 失敗時: ci-failureラベルが自動付与
  5. ラベル付与: CI修正ワークフローが自動実行
  6. Claude Code: 失敗原因を分析し、修正を実行
  7. 修正完了: コミットしてCI再実行

重要な設定ポイント

必要な権限

permissions:
  contents: write
  pull-requests: write
  issues: write
  id-token: write
  actions: read  # CI結果の読み取りに必要

PERSONAL_ACCESS_TOKEN (PAT) で動かす

github actions を動かす際に、通常の GITHUB_TOKEN を使うと、claude 側で権限がたりなかったり、そもそもトリガーされなかったりします。github actions で意図しない無限ループを防ぐための仕様です。そのため、今回のワークフロー構築では claude の起動トリガーとなる label やコメントの投稿などに PAT を使用しています。

必要なシークレット

  • CLAUDE_CODE_OAUTH_TOKEN: Claude Code APIトークン
  • PERSONAL_ACCESS_TOKEN: GitHub Personal Access Token

ラベル管理

  • ci-failure: CI失敗時に自動付与
  • ci-passed: CI成功時に自動付与
  • main-branch: mainブランチのCI失敗Issue用

注意点

  • Claude Codeの実行タイムアウトは60分に設定
  • mainブランチでのCI失敗は24時間以内の重複Issue作成を防止
  • 適切な権限設定が必要(特にactions: read)

終わりに

実は issue 発行からPR作成、マージまでの完全全自動開発ワークフローを作っていて、これはその一部分を切り出したものです。
実際は ci-passed のあとに CCA でレビューを入れたり、そのレビューに基づく修正を入れたり、自動マージしたりしています。完全自動については後日。

Discussion