はじめに

この記事は CyberAgent Developers Advent Calendar 2025 9 日目の記事です 🎅

こんにちは。ABEMA の広告配信システム開発チームでバックエンドを担当している戸田朋花です。

ABEMA の広告配信システムは大規模なシステムなため依存するパッケージが多いです。
セキュリティの観点から依存パッケージは定期的にバージョンアップして最新に近い状態を維持するのが重要です。しかし、依存が多いプロジェクトではひとつひとつ更新を確認し必要なバージョンアップを行う作業が負担になります。

この作業を自動化する方法の一つに Dependabot の活用があります。Dependabot とは GitHub で利用可能な依存関係を管理するためのツールで、リポジトリで使用している依存関係の脆弱性について通知したり、依存関係を最新に保つための Pull Request (PR)を自動で発行したりできます。 Dependabot を使うと依存関係更新の PR を自動で作成できます。
しかし、PR を確認して安全性を判断する作業は依然として人間が行う必要があり、完全な自動化はできていません。

一方で Dependabot による PR の多くは些細な変更で大抵は安全にマージできます。また確認が必要な観点も、新しいリリースに破壊的な変更があるか、更新された中で実際に利用している関数等に変更があるか、など明確に整理できます。

そこで、Dependabot の PR を AI でレビューすることで自動かつ安全にマージする GitHub Actions のワークフローを作成しました。

本記事ではこのワークフローの設計から実装までをご紹介します。

実現したいこと

まずは実現したいことについて整理します。
ABEMA ではトランクベースを意識した開発をしており、main ブランチは常にリリースできる状態を維持しています。そのため Dependabot の作成する PR のうちそのままマージしても安全にリリースできる PR について自動でマージしたいです。

「そのままマージしても安全にリリースできる PR」では曖昧なので具体化したところ、以下のような条件に整理できました。

  1. patch バージョンか minor バージョンしか上がっていないこと
  2. 信頼できる開発元が出しているパッケージであること
  3. CI が全て通過していること
  4. 破壊的な変更がないこと
  5. 実際に利用している関数等に直接的な影響がないこと

今回対象としているリポジトリでは、main にマージされたコードは自動で開発環境にデプロイされて動作確認ができます。本番環境へのリリースはリリースタグを作成するまで行われません。また、CI も充実しておりカバレッジが高い自動テストが行われています。
これらの背景から上の 5 つの条件さえ満たしていれば自動マージしても安全であると判断しています。

設計

上であげた条件のうちルールベースで対応できそうなものはルールベースで対応したいと考えました。1-3 はルールベースで簡単に判定できるため、残りの条件 4,5 を AI によるレビューの対象とします。

今回ワークフローを作成するリポジトリには Approve が 1 件以上あり、加えて CI が通過しないと PR をマージできないというブランチプロテクションルールがあります。このルールを活かしつつ自動マージを実現するため、以下の流れのワークフローを設計しました。

まず、ルールベースで判定できる条件(1, 2)が満たされた場合は Auto Merge を有効化します。
その上で条件 4,5 は AI にレビューさせ、問題ない場合は AI が Approve します。
AI による Approve と CI の通過(条件 3)が揃ったタイミングで Auto Merge が発火して、main ブランチに自動でマージされます。

この流れを図に起こすと以下のようになります。

ワークフローのフロー図

実装

全体のワークフローファイルは以下です。
重要な部分を抜き出して解説します。

name: Dependabot Claude Review

on:
  pull_request:
    types: [opened, synchronize]

jobs:
  review:
    if: >-
      ${{ github.event.pull_request.user.login == 'dependabot[bot]' && github.repository == '<REPOSITORY_NAME>' }}
    runs-on: ubuntu-slim
    permissions:
      contents: write
      pull-requests: write
      id-token: write
    env:
      WORKLOAD_IDENTITY_PROVIDER: <WORKLOAD_IDENTITY_PROVIDER>
      SERVICE_ACCOUNT: <SERVICE_ACCOUNT>
      CLAUDE_MODEL: <CLAUDE_MODEL>
      AUTOMERGE_DEPENDENCY_PREFIXES: |-
        <DEPENDENCY_PREFIX>
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 1

      - name: Authenticate to Google Cloud
        id: auth
        uses: google-github-actions/auth@v2
        with:
          workload_identity_provider: ${{ env.WORKLOAD_IDENTITY_PROVIDER }}
          service_account: ${{ env.SERVICE_ACCOUNT }}

      - name: Fetch Dependabot metadata
        id: metadata
        uses: dependabot/fetch-metadata@v2
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}

      - name: Ensure Dependabot directory and update type
        id: ensure-dependabot
        uses: actions/github-script@v8
        env:
          METADATA_UPDATE_TYPE: ${{ steps.metadata.outputs['update-type'] }}
          METADATA_DEPENDENCY_NAMES: ${{ steps.metadata.outputs['dependency-names'] }}
        with:
          script: |
            const skipWithNotice = (message) => {
              core.notice(message);
              core.setOutput('skip', 'true');
            };

            const updateType = (process.env.METADATA_UPDATE_TYPE || '').trim();

            if (
              updateType !== 'version-update:semver-minor' &&
              updateType !== 'version-update:semver-patch'
            ) {
              skipWithNotice(`Only semver minor/patch updates are allowed (got ${updateType}). Skipping.`);
              return;
            }

            const dependencyNames = (process.env.METADATA_DEPENDENCY_NAMES || '')
              .split(',')
              .map((name) => name.trim())
              .filter((line) => line.length > 0);

            const allowedPrefixes = (process.env.AUTOMERGE_DEPENDENCY_PREFIXES || '')
              .split(/\r?\n/)
              .map((line) => line.trim())
              .filter((line) => line.length > 0);

            for (const dependency of dependencyNames) {
              let matchesPrefix = false;
              for (const prefix of allowedPrefixes) {
                if (dependency.startsWith(prefix)) {
                  matchesPrefix = true;
                  break;
                }
              }

              if (!matchesPrefix) {
                skipWithNotice(`Dependency '${dependency}' does not match allowed prefixes. Skipping.`);
                return;
              }
            }

            core.setOutput('skip', 'false');

      - name: Enable auto-merge
        if: steps.ensure-dependabot.outputs.skip == 'false'
        env:
          GH_TOKEN: ${{ github.token }}
        run: >-
          gh pr merge ${{ github.event.pull_request.number }} --auto --merge --repo ${{ github.repository }}

      - name: Review changes with Claude
        if: steps.ensure-dependabot.outputs.skip == 'false'
        uses: anthropics/claude-code-action@v1
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          use_vertex: "true"
          prompt: |
            REPO: ${{ github.repository }}
            PR NUMBER: ${{ github.event.pull_request.number }}

            Perform a Dependabot dependency update safety review.

            Approve only if ALL:
            - PR body/release notes contain no breaking-change indicators.
            - Review the dependency diff against how the repository actually uses it and confirm the change is safe for that usage.

            Never approve if ANY:
            - PR body/release notes include breaking signals, e.g.: "BREAKING CHANGE", "breaking", "removal/removed", "rename", "unexport", "behavior/defaults change", "requires Go 1.", "drop support", "compatibility".

            Note: The PR branch is already checked out in the current working directory.

            Provide every GitHub comment in Japanese even though this prompt is in English.
            Only run the allowed commands listed below exactly as written (including order and flags).
            Use `gh pr comment ${{ github.event.number }} -b <string>` for top-level feedback.
            Use `mcp__github_inline_comment__create_inline_comment` to highlight specific code issues.
            Only post GitHub comments - don't submit review text as messages.
            If the update looks safe, approve the pull request with `gh pr review ${{ github.event.number }} -a`.
            If you have any doubts or discover issues, do not approve—leave explanatory comments instead.

            ---
              When posting the main review comment, output it in the following Markdown template:

              # dependabot による変更のレビュー

              ## 判定
              {{ verdict }}

              ## 対象
              {{ module_name }}@{{ from_version }} → {{ to_version }}(更新種別:{{ semver_type }})

              ## サマリー
              {{ summary }}

              ## 破壊的変更の有無
                - 状況:{{ breaking_change_status }}
                - 根拠:{{ breaking_change_reason }}

              ## 差分に関連する実装の実使用箇所

              - ファイル: {{ file_path }}:{{ line_number }}
                使用対象: {{ symbol_name }}
                利用内容: {{ how_it_is_used }}
                変更内容: {{ what_changed_in_dependency }}
                影響評価: {{ impact_level }}({{ reason_for_impact }})

              (If there are multiple affected locations, repeat the above block for each one.)
          claude_args: >-
            --allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment ${{ github.event.number }} -b:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr review ${{ github.event.number }} -a)"
            --max-turns 50
            --model ${{ env.CLAUDE_MODEL }}
            --debug
          show_full_output: "true"
        env:
          ANTHROPIC_VERTEX_PROJECT_ID: ${{ steps.auth.outputs.project_id }}
          CLOUD_ML_REGION: <CLOUD_ML_REGION>

以下のステップではfetch-metadata actionsを用いて Dependabot によって作成された PR の metadata の取得を行なっています。
これを利用すれば後続の条件の確認に必要な、アップデートの種類やアップデート対象の依存の名前を取得できます。

      - name: Fetch Dependabot metadata
        id: metadata
        uses: dependabot/fetch-metadata@v2
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}

次のステップでは GitHub Script を用いて JavaScript で条件 1, 2 のチェックを記述しています。
条件 1 のチェックは上のステップで取得したアップデートの種類が’version-update:semver-minor’もしくは’version-update:semver-patch’と一致しているかどうかを確かめることで実現しています。
条件 2 のチェックはAUTOMERGE_DEPENDENCY_PREFIXESに事前定義されている改行区切りの依存パッケージのプレフィックスのうちいずれかと上のステップで取得したアップデート対象の依存の名前が前方一致しているかどうか確かめることで実現しています。
もしこれらの条件を満たさない場合は skip=true の output を出力して後続のステップを skip できるようにしています。

      - name: Ensure Dependabot directory and update type
        id: ensure-dependabot
        uses: actions/github-script@v8
        env:
          METADATA_UPDATE_TYPE: ${{ steps.metadata.outputs['update-type'] }}
          METADATA_DEPENDENCY_NAMES: ${{ steps.metadata.outputs['dependency-names'] }}
        with:
          script: |
            const skipWithNotice = (message) => {
              core.notice(message);
              core.setOutput('skip', 'true');
            };

            const updateType = (process.env.METADATA_UPDATE_TYPE || '').trim();

            if (
              updateType !== 'version-update:semver-minor' &&
              updateType !== 'version-update:semver-patch'
            ) {
              skipWithNotice(`Only semver minor/patch updates are allowed (got ${updateType}). Skipping.`);
              return;
            }

            const dependencyNames = (process.env.METADATA_DEPENDENCY_NAMES || '')
              .split(',')
              .map((name) => name.trim())
              .filter((line) => line.length > 0);

            const allowedPrefixes = (process.env.AUTOMERGE_DEPENDENCY_PREFIXES || '')
              .split(/\r?\n/)
              .map((line) => line.trim())
              .filter((line) => line.length > 0);

            for (const dependency of dependencyNames) {
              let matchesPrefix = false;
              for (const prefix of allowedPrefixes) {
                if (dependency.startsWith(prefix)) {
                  matchesPrefix = true;
                  break;
                }
              }

              if (!matchesPrefix) {
                skipWithNotice(`Dependency '${dependency}' does not match allowed prefixes. Skipping.`);
                return;
              }
            }

            core.setOutput('skip', 'false');

次のステップでは PR の Auto Merge を gh コマンドで有効化しています。

      - name: Enable auto-merge
        if: steps.ensure-dependabot.outputs.skip == 'false'
        env:
          GH_TOKEN: ${{ github.token }}
        run: >-
          gh pr merge ${{ github.event.pull_request.number }} --auto --merge --repo ${{ github.repository }}

最後のステップでは AI によるレビューを行なっています。
AI によるレビューはClaude Code GitHub Actionsを用いて実現しました。Claude Code GitHub Actions は GitHub 上で簡単に Claude Code を利用できる Actions です。

API キーによるセットアップの他、Amazon Bedrock や Google Cloud Vertex AI によるセットアップが可能です。ABEMA では Google Cloud を主に使っているため今回は Google Cloud Vertex AI でセットアップしています。

プロンプトでは条件 4, 5 のレビューの観点の指示や最終的なアウトプットの指定を行なっています。
また、レビューして問題なかった場合は gh コマンドで Approve することも指示しています。

      - name: Review changes with Claude
        if: steps.ensure-dependabot.outputs.skip == 'false'
        uses: anthropics/claude-code-action@v1
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          use_vertex: "true"
          prompt: |
            REPO: ${{ github.repository }}
            PR NUMBER: ${{ github.event.pull_request.number }}

            Perform a Dependabot dependency update safety review.

            Approve only if ALL:
            - PR body/release notes contain no breaking-change indicators.
            - Review the dependency diff against how the repository actually uses it and confirm the change is safe for that usage.

            Never approve if ANY:
            - PR body/release notes include breaking signals, e.g.: "BREAKING CHANGE", "breaking", "removal/removed", "rename", "unexport", "behavior/defaults change", "requires Go 1.", "drop support", "compatibility".

            Note: The PR branch is already checked out in the current working directory.

            Provide every GitHub comment in Japanese even though this prompt is in English.
            Only run the allowed commands listed below exactly as written (including order and flags).
            Use `gh pr comment ${{ github.event.number }} -b <string>` for top-level feedback.
            Use `mcp__github_inline_comment__create_inline_comment` to highlight specific code issues.
            Only post GitHub comments - don't submit review text as messages.
            If the update looks safe, approve the pull request with `gh pr review ${{ github.event.number }} -a`.
            If you have any doubts or discover issues, do not approve—leave explanatory comments instead.

            ---
              When posting the main review comment, output it in the following Markdown template:

              # dependabot による変更のレビュー

              ## 判定
              {{ verdict }}

              ## 対象
              {{ module_name }}@{{ from_version }} → {{ to_version }}(更新種別:{{ semver_type }})

              ## サマリー
              {{ summary }}

              ## 破壊的変更の有無
                - 状況:{{ breaking_change_status }}
                - 根拠:{{ breaking_change_reason }}

              ## 差分に関連する実装の実使用箇所

              - ファイル: {{ file_path }}:{{ line_number }}
                使用対象: {{ symbol_name }}
                利用内容: {{ how_it_is_used }}
                変更内容: {{ what_changed_in_dependency }}
                影響評価: {{ impact_level }}({{ reason_for_impact }})

              (If there are multiple affected locations, repeat the above block for each one.)
          claude_args: >-
            --allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment ${{ github.event.number }} -b:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr review ${{ github.event.number }} -a)"
            --max-turns 50
            --model ${{ env.CLAUDE_MODEL }}
        env:
          ANTHROPIC_VERTEX_PROJECT_ID: ${{ steps.auth.outputs.project_id }}
          CLOUD_ML_REGION: <CLOUD_ML_REGION>

ワークフローの実行結果

自動マージされたケース

google.golang.org/api の minor バージョンアップがあった際の例です。

google.golang.org/apiのアップデートのPR

google.golang.org は信頼できる開発元として指定しており、minor アップデートだったため想定通り AI によるレビューが動きました。
以下のように指定したフォーマットに沿ってレビュー結果を出力し、Approve していることが確認できます。
Dependabot による PR の作成からマージまでたった 2 分で完了しています 🎉

Claude Codeによるレビュー結果

ルールベースの判定によりスキップされたケース

信頼できる開発元と指定していなかったパッケージに関するアップデートの PR の作成がされた時の例です。

信頼できる開発元として指定していない依存関係更新のPR

この際は以下のように Auto Merge の有効化のステップや Claude によるレビューのステップが意図通りスキップされています。

AIによるレビューがスキップされている様子

終わりに

本記事では Claude Code GitHub Actions を用いた Dependabot の PR マージの自動化についてご紹介しました。Claude Code GitHub Actions を用いることでワークフロー中で AI によるレビューを手軽に動かせました。

ルールベースで判断できるところとそうでないところを明確に分けてワークフローを組むことで、AI を活用した自動マージが安全に実現出来ました。今まではルールベースで判断できるものしか自動化できませんでしたが、生成 AI により「要件が明確だがルールベースで判断できない」性質のものも自動化できるようになっています。今回は Dependabot の PR のマージという形で実現されましたが、他にも色々活用できそうだと感じました。

また、今回このワークフローが簡単に実現できたのは、以前から CI やデプロイフローの整備がしっかりと行われていたのが大きな要因です。この試みを通して AI を活用すればなんでも簡単に便利になるのではなく、今までのソフトウェアエンジニアリングのベストプラクティスを丁寧に積み上げた先にこのような自動化が可能になると感じられました。

最後まで読んでいただきありがとうございました!!