Skip to content

Commit b57070a

Browse files
committed
feat: add PR inactivity reminder bot for stale pull requests
Signed-off-by: MonaaEid <monaa_eid@hotmail.com>
1 parent ece8844 commit b57070a

File tree

3 files changed

+174
-2
lines changed

3 files changed

+174
-2
lines changed
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// A script to remind PR authors of inactivity by posting a comment.
2+
3+
// DRY_RUN env var: any case-insensitive 'true' value will enable dry-run
4+
const dryRun = (process.env.DRY_RUN || 'false').toString().toLowerCase() === 'true';
5+
6+
// Helper to get the last commit date of a PR
7+
async function getLastCommitDate(github, pr, owner, repo) {
8+
const headRepoOwner = pr.head.repo?.owner?.login || owner;
9+
const headRepoName = pr.head.repo?.name || repo;
10+
try {
11+
const commitRes = await github.rest.repos.getCommit({
12+
owner: headRepoOwner,
13+
repo: headRepoName,
14+
ref: pr.head.sha,
15+
});
16+
const commit = commitRes.data?.commit ?? null;
17+
return new Date(commit?.author?.date || commit?.committer?.date || pr.created_at);
18+
} catch (getCommitErr) {
19+
console.log(`Failed to fetch head commit ${pr.head.sha} for PR #${pr.number}:`, getCommitErr.message || getCommitErr);
20+
return null; // Signal fallback needed
21+
}
22+
}
23+
24+
25+
// Look for an existing bot comment using our unique marker.
26+
async function hasExistingBotComment(github, pr, owner, repo, marker) {
27+
try {
28+
const comments = await github.paginate(github.rest.issues.listComments, {
29+
owner,
30+
repo,
31+
issue_number: pr.number,
32+
per_page: 100,
33+
});
34+
return comments.find(c => c.body && c.body.includes(marker)) || false;
35+
} catch (err) {
36+
console.log(`Failed to list comments for PR #${pr.number}:`, err.message || err);
37+
return null; // Prevent duplicate comment if we cannot check
38+
}
39+
}
40+
41+
// Helper to post an inactivity comment
42+
async function postInactivityComment(github, pr, owner, repo, marker, inactivityThresholdDays, discordLink, office_hours_calendar) {
43+
const comment = `${marker}
44+
Hi @${pr.user.login},\n\nThis pull request has had no commit activity for ${inactivityThresholdDays} days. Are you still working on the issue? please push a commit to keep the PR active or it will be closed due to inactivity.
45+
Reach out on discord or join our office hours if you need assistance.\n\n- ${discordLink}\n- ${office_hours_calendar} \n\nFrom the Python SDK Team`;
46+
if (dryRun) {
47+
console.log(`DRY-RUN: Would comment on PR #${pr.number} (${pr.html_url}) with body:\n---\n${comment}\n---`);
48+
return true;
49+
}
50+
51+
try {
52+
await github.rest.issues.createComment({
53+
owner,
54+
repo,
55+
issue_number: pr.number,
56+
body: comment,
57+
});
58+
console.log(`Commented on PR #${pr.number} (${pr.html_url})`);
59+
return true;
60+
} catch (commentErr) {
61+
console.log(`Failed to comment on PR #${pr.number}:`, commentErr);
62+
return false;
63+
}
64+
}
65+
66+
// Main module function
67+
module.exports = async ({github, context}) => {
68+
const inactivityThresholdDays = 10; // days of inactivity before commenting
69+
const cutoff = new Date(Date.now() - inactivityThresholdDays * 24 * 60 * 60 * 1000);
70+
const owner = context.repo.owner;
71+
const repo = context.repo.repo;
72+
const discordLink = `[Discord](https://github.com/hiero-ledger/hiero-sdk-python/blob/main/docs/discord.md)`;
73+
const office_hours_calendar =`[Office Hours](https://zoom-lfx.platform.linuxfoundation.org/meetings/hiero?view=week)`;
74+
// Unique marker so we can find the bot's own comment later.
75+
const marker = '<!-- pr-inactivity-bot-marker -->';
76+
77+
if (dryRun) {
78+
console.log('Running in DRY-RUN mode: no comments will be posted.');
79+
}
80+
81+
let commentedCount = 0;
82+
let skippedCount = 0;
83+
84+
const prs = await github.paginate(github.rest.pulls.list, {
85+
owner,
86+
repo,
87+
state: 'open',
88+
per_page: 100,
89+
});
90+
91+
for (const pr of prs) {
92+
// 1. Check inactivity
93+
const lastCommitDate = await getLastCommitDate(github, pr, owner, repo);
94+
if (lastCommitDate > cutoff) {
95+
skippedCount++;
96+
console.log(`PR #${pr.number} has recent commit on ${lastCommitDate.toISOString()} - skipping`);
97+
continue;
98+
}
99+
100+
// 2. Check for existing comment
101+
const existingBotComment = await hasExistingBotComment(github, pr, owner, repo, marker);
102+
if (existingBotComment) {
103+
skippedCount++;
104+
const idInfo = existingBotComment && existingBotComment.id ? existingBotComment.id : '(unknown)';
105+
console.log(`PR #${pr.number} already has an inactivity comment (id: ${idInfo}) - skipping`);
106+
continue;
107+
}
108+
109+
// 3. Post inactivity comment
110+
const commented = await postInactivityComment(github, pr, owner, repo, marker, inactivityThresholdDays, discordLink, office_hours_calendar);
111+
if (commented) commentedCount++;
112+
}
113+
114+
console.log("=== Summary ===");
115+
console.log(`PRs commented: ${commentedCount}`);
116+
console.log(`PRs skipped (existing comment present): ${skippedCount}`);
117+
};
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# This workflow warns PRs that have had no activity for a specified amount of time(10 Days).
2+
3+
name: PR Inactivity Reminder Bot
4+
5+
on:
6+
schedule:
7+
- cron: '0 11 * * *'
8+
workflow_dispatch:
9+
inputs:
10+
dry_run:
11+
description: 'If true, do not post comments (dry run). Accepts "true" or "false". Default true for manual runs.'
12+
required: false
13+
default: 'true'
14+
15+
permissions:
16+
pull-requests: write
17+
issues: write
18+
contents: read
19+
20+
21+
jobs:
22+
remind_inactive_prs:
23+
runs-on: ubuntu-latest
24+
env:
25+
DRY_RUN: ${{ github.event.inputs.dry_run || 'false' }}
26+
27+
steps:
28+
- name: Harden the runner
29+
uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3
30+
with:
31+
egress-policy: audit
32+
- name: Checkout repository
33+
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #v6.0.1
34+
- name: Remind authors of inactive PRs
35+
env:
36+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
37+
DRY_RUN: ${{ env.DRY_RUN }}
38+
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd #v8.0.0
39+
with:
40+
script: |
41+
const script = require('./.github/scripts/pr_inactivity_reminder.js')
42+
await script({ github, context });

CHANGELOG.md

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,28 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
88

99
### Added
1010

11+
- Added **str**() to CustomFixedFee and updated examples and tests accordingly.
12+
- Added a github template for good first issues
1113
- Added `.github/workflows/bot-assignment-check.yml` to limit non-maintainers to 2 concurrent issue assignments.
14+
- Added all missing fields to **str**() method and updated `test_tokem_info.py`
1215
- Add examples/tokens/token_create_transaction_pause_key.py example demonstrating token pause/unpause behavior and pause key usage (#833)
1316
- Added `docs/sdk_developers/training/transaction_lifecycle.md` to explain the typical lifecycle of executing a transaction using the Hedera Python SDK.
1417
- Add inactivity bot workflow to unassign stale issue assignees (#952)
18+
- Made custom fraction fee end to end
19+
- Added Acceptance Criteria section to Good First Issue template for better contributor guidance (#997)
20+
- Added __str__() to CustomRoyaltyFee and updated examples and tests accordingly (#986)
21+
- Add PR inactivity reminder workflow `pr-inactivity-reminder-bot.yml` for stale pull requests
22+
1523
### Changed
1624

25+
- Allow `PublicKey` for `TokenUpdateKeys` in `TokenUpdateTransaction`, enabling non-custodial workflows where operators can build transactions using only public keys (#934).
26+
- Bump protobuf toml to protobuf==6.33.2
27+
1728
### Fixed
1829

19-
-
30+
- Fixed inactivity bot workflow not checking out repository before running (#964)
31+
- Fixed the topic_message_query integarion test
32+
- good first issue template yaml rendering
2033

2134
### Breaking Change
2235

@@ -582,4 +595,4 @@ contract_call_local_pb2.ContractLoginfo -> contract_types_pb2.ContractLoginfo
582595

583596
### Removed
584597

585-
- N/A
598+
- N/A

0 commit comments

Comments
 (0)