Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions .github/workflows/dependabot-rebase.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Dependabot rebase workflow
# Copy to .github/workflows/dependabot-rebase.yml
#
# Requires repository secrets:
# APP_ID — GitHub App ID with contents:write and pull-requests:write
# APP_PRIVATE_KEY — GitHub App private key
#
# Problem: when branch protection requires branches to be up-to-date
# (strict status checks), merging one Dependabot PR makes the others fall
# behind. Dependabot does not auto-rebase PRs that are merely behind — it
# only rebases on its next scheduled run or when there are merge conflicts.
# This leaves auto-merge stalled until the next weekly Dependabot run.
#
# Solution: after every push to main (typically a merged PR), this workflow
# finds open Dependabot PRs that are behind and asks Dependabot to rebase
# them via the @dependabot rebase command. Dependabot performs the rebase
# with its own commit signature, preserving the automerge workflow's ability
# to verify the PR author.
#
# Important: never use the GitHub API update-branch endpoint to rebase
# Dependabot PRs — it replaces Dependabot's commit signature with GitHub's,
# which breaks dependabot/fetch-metadata verification and causes Dependabot
# to refuse future rebases on that PR.
name: Dependabot rebase behind PRs

on:
push:
branches:
- main

permissions: {}

jobs:
rebase:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
steps:
- name: Generate app token
id: app-token
uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}

- name: Rebase behind Dependabot PRs
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
REPO: ${{ github.repository }}
run: |
# Find open Dependabot PRs
PRS=$(gh pr list --repo "$REPO" --author "app/dependabot" \
--json number,headRefName \
--jq '.[] | "\(.number) \(.headRefName)"')

if [[ -z "$PRS" ]]; then
echo "No open Dependabot PRs"
exit 0
fi

while IFS=' ' read -r PR_NUMBER HEAD_REF; do
BEHIND=$(gh api "repos/$REPO/compare/main...$HEAD_REF" \

Copilot AI Apr 6, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HEAD_REF can contain slashes for Dependabot branches (e.g. dependabot/github_actions/...). In the compare API call, the head ref is part of the URL path, so unescaped / will break the request and leave BEHIND empty (causing the step to fail or skip rebases). URL-encode HEAD_REF (or switch to an API/GraphQL lookup that doesn’t require embedding the ref in the path) before calling repos/$REPO/compare/....

Suggested change
BEHIND=$(gh api "repos/$REPO/compare/main...$HEAD_REF" \
ENCODED_HEAD_REF=$(python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=""))' "$HEAD_REF")
BEHIND=$(gh api "repos/$REPO/compare/main...$ENCODED_HEAD_REF" \

Copilot uses AI. Check for mistakes.
--jq '.behind_by')

if [[ "$BEHIND" -gt 0 ]]; then
echo "PR #$PR_NUMBER ($HEAD_REF) is $BEHIND commit(s) behind — requesting rebase"
gh pr comment "$PR_NUMBER" --repo "$REPO" \
--body "@dependabot rebase"
Comment on lines +67 to +69

Copilot AI Apr 6, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will add a new @dependabot rebase comment on every push to main while a PR remains behind, which can spam PR timelines. Consider making the operation idempotent (e.g., edit the last bot comment instead of creating a new one, or check for an existing recent @dependabot rebase comment before posting).

Suggested change
echo "PR #$PR_NUMBER ($HEAD_REF) is $BEHIND commit(s) behind — requesting rebase"
gh pr comment "$PR_NUMBER" --repo "$REPO" \
--body "@dependabot rebase"
EXISTING_REBASE_COMMENT=$(gh api "repos/$REPO/issues/$PR_NUMBER/comments?per_page=100" \
--jq 'map(select(.body == "@dependabot rebase")) | length')
if [[ "$EXISTING_REBASE_COMMENT" -gt 0 ]]; then
echo "PR #$PR_NUMBER ($HEAD_REF) is $BEHIND commit(s) behind — rebase already requested"
else
echo "PR #$PR_NUMBER ($HEAD_REF) is $BEHIND commit(s) behind — requesting rebase"
gh pr comment "$PR_NUMBER" --repo "$REPO" \
--body "@dependabot rebase"
fi

Copilot uses AI. Check for mistakes.
else
echo "PR #$PR_NUMBER ($HEAD_REF) is up to date"
fi
done <<< "$PRS"
Loading