目次
前回の投稿では、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 RuleとGitHub 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 Role、Team、GitHub 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.lock | github-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]を追加 |
参考資料
- GitHub Docs - Managing pull requests for dependency updates
- dependabot-core #2798 - Add commit on dependabot pull request
- dependabot-core #2267 - Ability to ignore CI-generated commits
私のブログが役に立ちましたか?下にコメントを残してください。それは私にとって大きな大きな力になります!
アプリ広報
Dekuが開発したアプリを使ってみてください。Dekuが開発したアプリはFlutterで開発されています。興味がある方はアプリをダウンロードしてアプリを使ってくれると本当に助かります。