モノレポでDependabot PRが動作しない問題の解決

2025-02-05 hit count image

Yarn Workspacesベースのモノレポで、Branch Protection Ruleとlockfileの更新により Dependabotのrebaseが失敗する問題の原因と解決方法について紹介します。

github_actions

前回の投稿では、Dependabot PRを効率的に処理するためのSOPと自動化方法について学びました。

この投稿では、Yarn Workspacesベースのモノレポで、Dependabotを運用中に発生した問題と解決方法について紹介します。

背景

Yarn WorkspacesベースのモノレポでDependabotを運用中、2つの問題が同時に発生しました。

  • 症状1: @dependabot rebase 失敗
Oh no! Something went wrong on our end. Please try again later.
If the problem persists, please contact GitHub support for assistance
  • 症状2: Dependabotが自分のブランチを更新できない
Dependabot attempted to update this pull request, but because the branch
dependabot/github_actions/github-actions-946974bce4 is protected
it was unable to do so.

原因を調査した結果、Branch Protection RuleGitHub Actionsのlockfile更新方式の2つが絡み合っていました。

問題1: Branch Protection RuleがDependabotブランチを保護

原因

リポジトリに**/**パターンのBranch Protection Ruleが設定されていました。このワイルドカードパターンは全てのブランチにマッチするため、dependabot/...ブランチまで保護対象に含まれていました。

# 既存のBranch Protection Rules
**/**    → 全てのブランチ(Dependabot含む) ← 問題の原因
develop  → developブランチ専用
main     → mainブランチ専用

Dependabotブランチが保護されると、Dependabotは自分のブランチにpushできないため、rebaseやアップデートが不可能になります。

解決: **/**を具体的なパターンに分離

Branch Protection Rulesはexclude機能をサポートしていません。そのため、**/**からdependabot/**だけを除外することは不可能です。

解決方法は、**/**ルールを削除し、保護が必要なブランチパターンのみを個別に作成することです。私たちのプロジェクトではブランチの命名規則が{prefix}/{service}/{description}形式で決まっているため、各prefix別にルールを作成しました。

# 変更後のBranch Protection Rules
hotfix/**        ← 新規
release/**       ← 新規
review/**        ← 新規
feature/**       ← 新規
fix/**           ← 新規
sub-feature/**   ← 新規
merge/**         ← 新規
develop          ← 既存維持
main             ← 既存維持

dependabot/**パターンは作成しないため、Dependabotブランチは自然と保護対象から除外されます。

GitHub GraphQL APIで一括作成

7つのルールを手動で作成するのは手間がかかるため、GraphQL APIを使用して一括作成しました。

# リポジトリID取得
REPO_ID=$(gh api graphql -f query='
  { repository(owner: "ORG_NAME", name: "REPO_NAME") { id } }
' --jq '.data.repository.id')

# バイパス対象ユーザーのNode ID取得
gh api "users/USERNAME" --jq '.node_id'

# 7つのルールを一括作成
for pattern in "hotfix/**" "release/**" "review/**" \
               "feature/**" "fix/**" "sub-feature/**" "merge/**"; do
  gh api graphql -f query='
    mutation {
      createBranchProtectionRule(input: {
        repositoryId: "'"$REPO_ID"'"
        pattern: "'"$pattern"'"
        requiresApprovingReviews: true
        requiredApprovingReviewCount: 2
        requiresCodeOwnerReviews: true
        dismissesStaleReviews: false
        requiresConversationResolution: true
        requiresStatusChecks: true
        requiredStatusCheckContexts: []
        allowsForcePushes: true
        allowsDeletions: true
        isAdminEnforced: false
        bypassPullRequestActorIds: ["USER_NODE_ID_1", "USER_NODE_ID_2"]
      }) {
        branchProtectionRule { id pattern }
      }
    }'
done

新しいルールを全て作成した後、既存の**/**ルールを削除します。

# 既存ルールID取得
gh api graphql -f query='{
  repository(owner: "ORG_NAME", name: "REPO_NAME") {
    branchProtectionRules(first: 20) {
      nodes { pattern id }
    }
  }
}'

# 削除
gh api graphql -f query='
  mutation {
    deleteBranchProtectionRule(input: {
      branchProtectionRuleId: "BPR_XXXXXXXXX"
    }) { clientMutationId }
  }'

代替案: Rulesetsの使用

GitHubの新しいRulesets機能を使用すると、excludeパターンを指定できます。~ALL(全ブランチ)をincludeし、refs/heads/dependabot/**/*をexcludeする方式です。

ただし、RulesetsとBranch Protection Rulesは**独立して動作(additive)**します。Rulesetsを追加しても既存のBranch Protection Rulesはそのまま適用されるため、必ず既存の**/**ルールを削除する必要があります。

また、RulesetsのBypass listは個別ユーザーを直接追加できず、Repository RoleTeamGitHub App単位でのみ設定できる点も注意が必要です。

検証

変更後、Dependabotブランチの保護状態を確認します。

gh api "repos/ORG/REPO/branches/dependabot%2F..." --jq '.protected'
# false → 正常

問題2: GitHub Actionsのlockfile更新がDependabot rebaseをブロック

Branch Protection Ruleの問題を解決した後も、@dependabot rebaseは依然として失敗していました。

Looks like this PR has been edited by someone other than Dependabot.
That means Dependabot can't rebase it - sorry!

原因

モノレポでは、Dependabotは各アプリのpackage.jsonのみを更新し、ルートのyarn.lockは更新できません。これを補うため、前回の投稿で紹介したGitHub Actionsワークフローでyarn installを実行し、yarn.lockの変更分をコミットしていました。

# dependabot_actions.yml
- name: Commit updated lockfile
  run: |
    git config user.name "github-actions[bot]"
    git config user.email "github-actions[bot]@users.noreply.github.com"
    git add yarn.lock
    git diff --staged --quiet || git commit -m "chore: update yarn.lock"
    git push

これにより、Dependabotブランチのコミット履歴は以下のようになります。

コミットAuthor
deps(pos): bump @types/node...dependabot[bot]
chore: update yarn.lockgithub-actions[bot]

Dependabotはサーバー側で自分がpushしたHEAD SHAを追跡しています。 追加コミットによりHEAD SHAが変更されると、コミットauthorが誰であっても「edited by someone other than Dependabot」と判断し、rebaseを拒否します。

解決: [dependabot skip] コミットメッセージ

GitHub公式ドキュメントでこの問題の解決策が提供されています。コミットメッセージに[dependabot skip]を含めると、Dependabotはそのコミットを「一時的なコミット」と見なし、rebase時にforce-pushで上書きします。

# Before
git diff --staged --quiet || git commit -m "chore: update yarn.lock"

# After
git diff --staged --quiet || git commit -m "[dependabot skip] chore: update yarn.lock"

動作フロー

1. DependabotがPR作成            → コミットA
2. GitHub Actionがyarn.lock更新  → コミットB [dependabot skip]
3. @dependabot rebase 実行
4. Dependabotがrebase実行        → コミットA'(コミットBはforce-pushで削除)
5. synchronizeイベント発生
6. GitHub Actionが再実行         → コミットB' [dependabot skip]

rebase時に[dependabot skip]コミットは消えますが、ワークフローがsynchronizeイベントに反応するため、自動的に再作成されます。

既存PRへの対応

すでに[dependabot skip]なしでコミットがpushされた既存PRは@dependabot rebaseができません。この場合は@dependabot recreateでPRを再作成する必要があります。

まとめ

問題原因解決
Dependabotがブランチを更新できない**/** Branch Protection RuleがDependabotブランチまで保護具体的なブランチパターンに分離してDependabotブランチを保護対象から除外
@dependabot rebase 失敗GitHub Actionsのlockfile更新コミットがDependabotのHEAD SHA追跡を妨害コミットメッセージに[dependabot skip]を追加

参考資料

私のブログが役に立ちましたか?下にコメントを残してください。それは私にとって大きな大きな力になります!

アプリ広報

今見てるブログを作成たDekuが開発したアプリを使ってみてください。
Dekuが開発したアプリはFlutterで開発されています。

興味がある方はアプリをダウンロードしてアプリを使ってくれると本当に助かります。



SHARE
Twitter Facebook RSS