概要
依存関係の自動更新とは、プロジェクトが依存するライブラリの新しいバージョンをツールに検知させ、更新用のプルリクエストを自動で作らせる運用です。代表的なツールにDependabotとRenovateがあります。 ツールが行うのはプルリクエストの作成までです。そのプルリクエストがレビューされ、マージされるかどうかはチームの運用で決まります。なぜ依存関係を更新し続けるのか
更新しない依存関係には、2種類のコストが溜まっていきます。 1つ目は脆弱性への露出です。脆弱性は公開済みのバージョンで後から見つかります。修正は新しいバージョンにしか入らないことが多く、更新が大きく遅れているプロジェクトは、まず追いつかないと修正を適用できません。 2つ目は追従コストの増大です。更新を見送るたびに、後で取り込む変更が増えます。メジャーバージョンを複数またぐと破壊的変更が重なり、通常のマージで済んだはずの更新が独立したプロジェクトになります。 依存関係は、まとめて大きく更新するより、こまめに少しずつ更新するほうが手間が少なく済みます。パッチリリースは変更が少ないため確認が早く、問題が起きても原因の切り分けと切り戻しが容易です。 自動更新ツールが作る更新プルリクエストには2種類あります。
代表的な2つのツールは、主に提供形態と設定の方法が異なります。
更新プルリクエストを捌く
頻度とまとめ方
更新の頻度は、チームが実際にレビューできる量から決めます。毎週が一般的な基準です。すべてのエコシステムを毎日更新すると、読み切れない量のプルリクエストが届き、未対応のものが溜まっていきます。 それでも量が多い場合は、次のように分散させます。- 変更の少ないリポジトリは、隔週や月1回に頻度を下げます。
- エコシステムごとに曜日を変えます(例: アプリケーションのライブラリは月曜、CIのアクションは火曜)。レビューが一度に集中しなくなります。
- 同時に開いておける更新プルリクエストの数に上限を設けます。
groupsでまとめる単位を定義します。次の例では、@vitest/coverage-v8などがvitestのバージョンを完全一致で指定しているため、vitestと@vitest/*を1つのグループにしています。
.github/dependabot.yml
groupsはデフォルトではバージョン更新にだけ適用され、セキュリティ更新もまとめるにはapplies-to: security-updatesを指定したグループが別途必要です。
無関係なパッケージを更新の種類だけでまとめる(「minorとpatchの更新をすべて1つに」)のは避けます。プルリクエストのレビューが難しくなり、1つのパッケージで問題が起きたときにそれだけを切り戻せません。
新しいバージョンをすぐに取り込まない
公開から一定の日数が経ったバージョンだけを更新対象にする待機期間を設定します。悪意のあるリリースや不具合のあるリリースは、公開から数日以内に発見されて取り下げられることがよくあります。待機期間を設けておけば、そうしたリリースをプロジェクトに取り込まずに済みます。.github/dependabot.yml
minimumReleaseAgeが同じ役割の設定です。ただし、セキュリティ更新は待たせません。待機期間はバージョン更新にだけ適用し、既知の脆弱性の修正はすぐに取り込めるようにします。
パッケージマネージャー側にも公開からの最小経過日数の制限がある場合は、更新ツールの待機期間をそれより少し長くします。同じ値にすると、ツールが提案したバージョンをパッケージマネージャーがインストール時に拒否し、CIが失敗することがあります。
マージ前の確認項目
更新プルリクエストをマージする前に、次を確認します。- リリースノート。 現在のバージョンから新しいバージョンまでの変更を読み、破壊的変更、非推奨化、デフォルト値の変更がないかを確認します。
- 影響するコード。 変更されたAPIやオプションをプロジェクトが使っているかを確認します。
- CIの結果。 ビルド、lint、型チェック、テストが通っていることを確認します。CIが保証するのはテストが検証している範囲だけなので、この確認の価値はテストスイートの質で決まります。
- ロックファイルの差分。 想定外の間接依存が入っていないことを確認します。
自動マージの範囲
自動マージは、定めた条件を満たした更新プルリクエストを、人を介さずにマージする仕組みです。日々届く小さな更新を捌くのに効果がありますが、CIを信頼できることが前提です。依存関係が影響するコードをテストが検証していなければ、CIが通っても更新が安全とは限りません。その状態で自動マージを使うと、問題のある更新が誰にも確認されないままマージされます。 自動マージの範囲は、次の3つの条件を組み合わせて決めます。
最初は範囲を狭くし、テストスイートへの信頼が高まるにつれて広げます。
0.xのバージョンは対象から外します。0.xは初期開発版で、セマンティックバージョニングではどの更新にも破壊的変更を含めてよいためです。メジャー更新は人の判断に残します。
Renovateには組み込みの自動マージ(automerge)があり、設定で対象を絞れます。Dependabotには組み込みの機能がないため、自動マージをGitHub Actionsで実装します。dependabot/fetch-metadataで更新の種類を読み取り、gh pr merge --autoで必須チェックが通った時点でマージされるようにします。ブランチに必須のステータスチェックがないと、マージ条件がすでに満たされた扱いになり、--autoを指定しても待たずにマージされます。先にブランチ保護で必須チェックを設定しておきます。
ファインディでは、上の表の条件にAIの判定を加えて自動マージしているチームもあります。条件は、パッチ更新または社内製パッケージだけの更新であること、AIが問題なしと判定したこと、CIが通ったことの3つです。
レビュー担当と通知先を決める
担当者のいない更新プルリクエストは放置されます。自動マージの対象にならなかったプルリクエストに誰が対応するか、チームにどこで知らせるかを決めておきます。 レビュー担当は、ツールの設定でレビュアーのチームを指定するか、メンバーの間で一定の周期で当番を回して決めます。当番制にすると作業が偏らず、今週の担当者が明確になります。 割り当ては、更新ツールの実行後の決まった曜日に、開いているプルリクエストへまとめて行います。こうすると、担当者は1件ずつ届く通知に追われずに対応できます。 割り当てた結果は、担当者をメンションしてチームのチャンネルに通知します。更新プルリクエストにはラベルを付け、絞り込めるようにしておきます。AIエージェントに確認を任せる
マージ前の確認項目は毎回同じ手順で進むため、AIエージェントに任せやすい作業です。更新プルリクエストごとにエージェントを自動で実行すれば、手作業の確認で生じるコンテキストスイッチと、確認する人による観点のばらつきを減らせます。任せる確認項目
エージェントが行う手順は、人がマージ前に確認するときと同じです。 まず、プルリクエストから対象のパッケージとバージョンの範囲を特定し、その範囲の公式リリースノートと変更履歴を読みます。次に、変更されたAPIの利用箇所をコードベースから探し、CIの結果を確認します。失敗したジョブがあれば、そのログも読みます。 最後に、リスクと必要な対応をまとめます。 エージェントには、リリースノートの「破壊的変更なし」といった記載ではなく、変更内容そのものから判断するよう指示します。リリースノートやプルリクエストの本文は外部からの入力であり、そこに書かれた文章を指示として扱ってはいけません。結果の形式を揃える
レビュアーが判定が一目で分かるように、どの更新でも同じ形式でコメントさせます。実行タイミング
CIの完了後にエージェントを実行し、CIの結果も判断材料にさせます。CIが失敗しても実行します。失敗ログを読むこともエージェントに任せられる作業の一部です。CIの結果を取得できない場合は承認させません。 エージェントに与える権限は最小限にします。エージェントの役割は、判定結果を書き出すところまでです。承認やマージは別のステップが担当し、エージェントの判定に加えて、更新の種類・CIの結果・パッケージの出どころといった条件を機械的に確かめてから実行します。条件のどれかを確かめられない場合は、承認もマージもせずに人に任せます。 GitHubでは、Dependabotが起動したワークフローは通常のActionsのシークレットを読めません。エージェントが使うAPIキーは、Dependabot用のシークレットにも登録します。人間に残る判断
人が最終判断するのは、エージェントが「確認が必要」または「要対応」と判定した更新、メジャー更新、自動マージの範囲外の更新です。エージェントに任せられるのは定型的な確認作業までで、マージしてよいかの判断は人に残ります。特定の種類の更新でエージェントの誤判定が続く場合は、その種類の更新に関する指示を見直します。関連ページ
CI/CD
更新プルリクエストをマージしてよいかを決めるチェックは、このパイプラインで動きます。
テストコード
自動マージをどこまで広げられるかは、テストスイートが実際に何を守っているかで決まります。
コードレビュー
機械的なチェックと人の判断を分ける考え方を、すべてのプルリクエストに適用します。