Skip to content
Draft
Show file tree
Hide file tree
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
147 changes: 147 additions & 0 deletions .github/scripts/issue_reminder_no_pr.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
#!/usr/bin/env bash
set -euo pipefail

# Env:
# GH_TOKEN - provided by GitHub Actions
# REPO - owner/repo (fallback to GITHUB_REPOSITORY)
# DAYS - reminder threshold in days (default 7)
# DRY_RUN - if "true", only log actions without posting comments

REPO="${REPO:-${GITHUB_REPOSITORY:-}}"
DAYS="${DAYS:-7}"
DRY_RUN="${DRY_RUN:-false}"

if [ -z "$REPO" ]; then
echo "ERROR: REPO environment variable not set."
exit 1
fi

echo "------------------------------------------------------------"
echo " Issue Reminder Bot (No PR)"
echo " Repo: $REPO"
echo " Threshold: $DAYS days"
echo " Dry Run: $DRY_RUN"
echo "------------------------------------------------------------"
echo

NOW_TS=$(date +%s)

# Cross-platform timestamp parsing (Linux + macOS/BSD)
parse_ts() {
local ts="$1"
if date --version >/dev/null 2>&1; then
date -d "$ts" +%s # GNU date (Linux)
else
date -j -f "%Y-%m-%dT%H:%M:%SZ" "$ts" +"%s" # macOS/BSD
fi
}

# Fetch open ISSUES (not PRs) that have assignees
ISSUES=$(gh api "repos/$REPO/issues" \
--paginate \
--jq '.[] | select(.state=="open" and (.assignees | length > 0) and (.pull_request | not)) | .number')

if [ -z "$ISSUES" ]; then
echo "No open issues with assignees found."
exit 0
fi

for ISSUE in $ISSUES; do
echo "============================================================"
echo " ISSUE #$ISSUE"
echo "============================================================"

ISSUE_JSON=$(gh api "repos/$REPO/issues/$ISSUE")
ASSIGNEES=$(echo "$ISSUE_JSON" | jq -r '.assignees[].login')

if [ -z "$ASSIGNEES" ]; then
echo "[INFO] No assignees? Skipping."
echo
continue
fi

echo "[INFO] Assignees: $ASSIGNEES"
echo

# Check if this issue already has a reminder comment from ReminderBot
EXISTING_COMMENT=$(gh api "repos/$REPO/issues/$ISSUE/comments" \
--jq ".[] | select(.user.login == \"github-actions[bot]\") | select(.body | contains(\"ReminderBot\")) | .id" \
| head -n1)

if [ -n "$EXISTING_COMMENT" ]; then
echo "[INFO] Reminder comment already posted on this issue."
echo
continue
fi

# Get assignment time (use the last assigned event)
ASSIGN_TS=$(gh api "repos/$REPO/issues/$ISSUE/events" \
--jq ".[] | select(.event==\"assigned\") | .created_at" \
| tail -n1)

if [ -z "$ASSIGN_TS" ]; then
echo "[WARN] No assignment event found. Skipping."
continue
fi

ASSIGN_TS_SEC=$(parse_ts "$ASSIGN_TS")
DIFF_DAYS=$(( (NOW_TS - ASSIGN_TS_SEC) / 86400 ))

echo "[INFO] Assigned at: $ASSIGN_TS"
echo "[INFO] Days since assignment: $DIFF_DAYS"

# Check if any open PRs are linked to this issue
PR_NUMBERS=$(gh api \
-H "Accept: application/vnd.github.mockingbird-preview+json" \
"repos/$REPO/issues/$ISSUE/timeline" \
--jq ".[]
| select(.event == \"cross-referenced\")
| select(.source.issue.pull_request != null)
| .source.issue.number" 2>/dev/null || true)

OPEN_PR_FOUND=""
if [ -n "$PR_NUMBERS" ]; then
for PR_NUM in $PR_NUMBERS; do
PR_STATE=$(gh pr view "$PR_NUM" --repo "$REPO" --json state --jq '.state' 2>/dev/null || true)
if [ "$PR_STATE" = "OPEN" ]; then
OPEN_PR_FOUND="$PR_NUM"
break
fi
done
fi

if [ -n "$OPEN_PR_FOUND" ]; then
echo "[KEEP] An OPEN PR #$OPEN_PR_FOUND is linked to this issue → skip reminder."
echo
continue
fi

echo "[RESULT] No OPEN PRs linked to this issue."

# Check if threshold has been reached
if [ "$DIFF_DAYS" -lt "$DAYS" ]; then
echo "[WAIT] Only $DIFF_DAYS days (< $DAYS) → not yet time for reminder."
echo
continue
fi

echo "[REMIND] Issue #$ISSUE assigned for $DIFF_DAYS days, posting reminder."

# Post reminder comment
MESSAGE="Hi, this is ReminderBot. This issue has been assigned but has had no pull request created. Are you still planning on working on the issue?

From the Python SDK Team"

if [ "$DRY_RUN" = "true" ]; then
echo "[DRY RUN] Would post comment on issue #$ISSUE:"
echo "$MESSAGE"
else
gh issue comment "$ISSUE" --repo "$REPO" --body "$MESSAGE"
echo "[DONE] Posted reminder comment on issue #$ISSUE."
fi
echo
done

echo "------------------------------------------------------------"
echo " Issue Reminder Bot (No PR) complete."
echo "------------------------------------------------------------"
38 changes: 38 additions & 0 deletions .github/workflows/bot-issue-reminder-no-pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: bot-issue-reminder-no-pr

on:
schedule:
- cron: "0 10 * * *" #runs daily at 10:00 UTC
workflow_dispatch:
inputs:
dry_run:
description: "Dry run (log only, do not post comments)"
required: false
Copy link
Contributor

Choose a reason for hiding this comment

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

either make it really clear DRY run can only be true or false, or, allow yes/1 too

this is important because some of our dry runs use 1, but if a user types those in now, it will not pick them up and assign false

default: true #safe default for manual testing
type: boolean

permissions:
contents: read
issues: write #needed to comment on issues
pull-requests: read #needed to check PR state

jobs:
reminder:
runs-on: ubuntu-latest

steps:
- name: Harden the runner
uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2
with:
egress-policy: audit

- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 #6.0.1

- name: Post reminder on assigned issues with no PRs
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
DAYS: 7
DRY_RUN: ${{ inputs.dry_run }}
Copy link
Contributor

Choose a reason for hiding this comment

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

You are passing
DRY_RUN="" i think
So on scheduled runs it passes empty
then inside the script it turns false in line 12

i think it will be clearer if we pass false here on workflow runs so the user knows what the workflow will do

run: bash .github/scripts/issue_reminder_no_pr.sh
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
- Unified the inactivity-unassign bot into a single script with `DRY_RUN` support, and fixed handling of cross-repo PR references for stale detection.
- Added unit tests for `SubscriptionHandle` class covering cancellation state, thread management, and join operations.
- Refactored `account_create_transaction_create_with_alias.py` example by splitting monolithic function into modular functions: `generate_main_and_alias_keys()`, `create_account_with_ecdsa_alias()`, `fetch_account_info()`, `print_account_summary()` (#1016)
-
- Modularized `transfer_transaction_fungible` example by introducing `account_balance_query()` & `transfer_transaction()`.Renamed `transfer_tokens()` → `main()`
- Phase 2 of the inactivity-unassign bot: Automatically detects stale open pull requests (no commit activity for 21+ days), comments with a helpful InactivityBot message, closes the stale PR, and unassigns the contributor from the linked issue.
- Added `__str__()` to CustomFixedFee and updated examples and tests accordingly.
Expand All @@ -35,6 +34,7 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
- Add PR inactivity reminder bot for stale pull requests `.github/workflows/pr-inactivity-reminder-bot.yml`
- Add comprehensive training documentation for _Executable class `docs/sdk_developers/training/executable.md`
- Added empty `docs/maintainers/good_first_issues.md` file for maintainers to write Good First Issue guidelines (#1034)
- Added Issue Reminder (no-PR) bot, `.github/scripts/issue_reminder_no_pr.sh` and `.github/workflows/bot-issue-reminder-no-pr.yml` to automatically detect assigned issues with no linked pull requests for 7+ days and post a gentle ReminderBot comment.(#951)

### Changed

Expand Down