Skip to content

Commit ff87261

Browse files
fredbiclaude
andauthored
feat: add workflow to monitor stalled bot PRs (#201)
When a repository evolves quickly, bot-authored PRs with auto-merge enabled can fall behind the base branch and never merge. monitor-bot-pr.yml is a reusable workflow that, on a schedule, finds such stalled PRs (open, auto-merge enabled, BEHIND base, idle for at least one day, no failed CI) and unblocks them: * dependabot PRs are commented "@dependabot rebase", so dependabot rebases the branch and CI restarts; * organization bot PRs are rebased directly via "gh pr update-branch --rebase", which restarts CI. PRs for dependency groups that are not configured for auto-merge are excluded by the autoMergeRequest != null filter. local-monitor-bot-pr.yml exercises the shared workflow daily, mirroring how consuming go-openapi repositories will invoke it. Signed-off-by: Frédéric BIDON <fredbi@yahoo.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 39c687e commit ff87261

2 files changed

Lines changed: 168 additions & 0 deletions

File tree

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name: Monitor bot PRs [Test only]
2+
3+
# description: |
4+
# This workflow mimics how a go-openapi repo would typically invoke the common workflow.
5+
6+
on:
7+
workflow_dispatch:
8+
schedule:
9+
- cron: '18 6 * * *'
10+
11+
permissions:
12+
contents: read
13+
14+
jobs:
15+
monitor-pr:
16+
permissions:
17+
contents: write
18+
pull-requests: write
19+
uses: ./.github/workflows/monitor-bot-pr.yml
20+
secrets: inherit
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
name: Monitor bot PRs
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
enable-organization-bot:
7+
description: |
8+
Enable detection and rebase of stalled PRs initiated by the organization bot.
9+
10+
type: string
11+
required: false
12+
default: "true"
13+
organization-bot:
14+
description: |
15+
The bot user that opens pull requests on behalf of the organization,
16+
whose stalled PRs should be rebased automatically.
17+
18+
Example: bot-go-openapi[bot]
19+
20+
type: string
21+
required: false
22+
default: "bot-go-openapi[bot]"
23+
24+
permissions:
25+
contents: read
26+
27+
defaults:
28+
run:
29+
shell: bash
30+
31+
jobs:
32+
dependabot-prs:
33+
permissions:
34+
contents: read
35+
pull-requests: write
36+
runs-on: ubuntu-latest
37+
env:
38+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
39+
REPO: ${{ github.repository }}
40+
steps:
41+
-
42+
name: Request a rebase on stalled dependabot PRs
43+
# A stalled PR is one that should have auto-merged but did not, because
44+
# its branch fell behind the base ref. We detect it when all of:
45+
# * the PR is open and authored by the dependabot bot,
46+
# * auto-merge is enabled (autoMergeRequest is not null) -- this
47+
# excludes dependency groups that are deliberately not auto-merged,
48+
# * it has not been updated for at least one day,
49+
# * its merge state is BEHIND (head ref needs to catch up to base),
50+
# * no CI check has failed.
51+
# GitHub never rebases dependabot branches by itself, so we comment
52+
# "@dependabot rebase". Dependabot rebases the branch, CI restarts and
53+
# auto-merge is evaluated again.
54+
run: |
55+
cutoff="$(date -u -d '1 day ago' +%Y-%m-%dT%H:%M:%SZ)"
56+
echo "::notice title=monitor::Looking for stalled dependabot PRs not updated since ${cutoff}"
57+
58+
mapfile -t urls < <(
59+
gh pr list --repo "${REPO}" \
60+
--state open --limit 10 \
61+
--author 'dependabot[bot]' \
62+
--json url,author,autoMergeRequest,mergeStateStatus,updatedAt,statusCheckRollup \
63+
| jq -r --arg cutoff "${cutoff}" '
64+
.[]
65+
| select(.author.is_bot)
66+
| select(.autoMergeRequest != null)
67+
| select(.updatedAt <= $cutoff)
68+
| select(.mergeStateStatus == "BEHIND")
69+
| select(
70+
[.statusCheckRollup[]? | (.conclusion // .state // "")]
71+
| map(. == "FAILURE" or . == "ERROR" or . == "TIMED_OUT" or . == "CANCELLED" or . == "ACTION_REQUIRED" or . == "STARTUP_FAILURE")
72+
| any
73+
| not
74+
)
75+
| .url
76+
'
77+
)
78+
79+
if [ "${#urls[@]}" -eq 0 ]; then
80+
echo "::notice title=monitor::No stalled dependabot PRs found"
81+
exit 0
82+
fi
83+
84+
for pr_url in "${urls[@]}"; do
85+
echo "::notice title=rebase::Requesting dependabot rebase for ${pr_url}"
86+
gh pr comment "${pr_url}" --body "@dependabot rebase"
87+
done
88+
89+
organization-bot-prs:
90+
if: ${{ inputs.enable-organization-bot == 'true' }}
91+
permissions:
92+
contents: write
93+
pull-requests: write
94+
runs-on: ubuntu-latest
95+
env:
96+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
97+
REPO: ${{ github.repository }}
98+
ORG_BOT: ${{ inputs.organization-bot }}
99+
steps:
100+
-
101+
name: Rebase the branch of stalled organization bot PRs
102+
# Same detection as for dependabot PRs. The organization bot does not
103+
# answer rebase comments, so we ask GitHub to update the branch directly
104+
# with "gh pr update-branch --rebase". This requires the PR branch to live
105+
# in this repository (not a fork). The rebase restarts CI and auto-merge
106+
# is evaluated again.
107+
run: |
108+
cutoff="$(date -u -d '1 day ago' +%Y-%m-%dT%H:%M:%SZ)"
109+
echo "::notice title=monitor::Looking for stalled ${ORG_BOT} PRs not updated since ${cutoff}"
110+
111+
mapfile -t urls < <(
112+
gh pr list --repo "${REPO}" \
113+
--state open --limit 10 \
114+
--author "${ORG_BOT}" \
115+
--json url,author,autoMergeRequest,mergeStateStatus,updatedAt,statusCheckRollup \
116+
| jq -r --arg cutoff "${cutoff}" '
117+
.[]
118+
| select(.author.is_bot)
119+
| select(.autoMergeRequest != null)
120+
| select(.updatedAt <= $cutoff)
121+
| select(.mergeStateStatus == "BEHIND")
122+
| select(
123+
[.statusCheckRollup[]? | (.conclusion // .state // "")]
124+
| map(. == "FAILURE" or . == "ERROR" or . == "TIMED_OUT" or . == "CANCELLED" or . == "ACTION_REQUIRED" or . == "STARTUP_FAILURE")
125+
| any
126+
| not
127+
)
128+
| .url
129+
'
130+
)
131+
132+
if [ "${#urls[@]}" -eq 0 ]; then
133+
echo "::notice title=monitor::No stalled ${ORG_BOT} PRs found"
134+
exit 0
135+
fi
136+
137+
for pr_url in "${urls[@]}"; do
138+
echo "::notice title=rebase::Rebasing branch for ${pr_url}"
139+
set +e
140+
output="$(gh pr update-branch --rebase "${pr_url}" 2>&1)"
141+
code=$?
142+
set -e
143+
if [ "${code}" -ne 0 ]; then
144+
echo "::warning title=rebase::Could not rebase ${pr_url}: ${output}"
145+
continue
146+
fi
147+
echo "${output}"
148+
done

0 commit comments

Comments
 (0)