Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
edd08cf
add stale discussion check
mvadari Sep 5, 2025
9968c96
use sofa script
mvadari Sep 5, 2025
5f4f3aa
Merge branch 'master' into discussion-management
mvadari Sep 5, 2025
a0691b4
oops
mvadari Sep 5, 2025
d92cf91
change owner
mvadari Sep 5, 2025
8fd37d6
update script link
mvadari Sep 5, 2025
096d22a
Merge branch 'master' into discussion-management
mvadari Sep 8, 2025
5b1103d
attempt #2
mvadari Sep 9, 2025
0c97dbf
run forreal (fingers crossed)
mvadari Sep 9, 2025
346a2e6
fix
mvadari Sep 9, 2025
6a46309
fix
mvadari Sep 9, 2025
32980e8
try again
mvadari Sep 9, 2025
85b299e
better error handling
mvadari Sep 9, 2025
5b3909d
fix hopefully?
mvadari Sep 9, 2025
00bfb34
Merge remote-tracking branch 'upstream/master' into discussion-manage…
mvadari Sep 9, 2025
097b2b3
try different URL
mvadari Sep 9, 2025
474fcbf
switch back to graphql
mvadari Sep 9, 2025
e0b524f
try again
mvadari Sep 9, 2025
4adf9d5
accidentally removed run
mvadari Sep 9, 2025
e191f7c
try again
mvadari Sep 9, 2025
8b46fbe
try again
mvadari Sep 9, 2025
a0dcde5
augment attempt
mvadari Dec 17, 2025
e67265c
change to updated, exclude closed
mvadari Dec 17, 2025
dd79f39
Merge branch 'master' into discussion-management
mvadari Dec 17, 2025
9981bd1
test out full flow
mvadari Dec 17, 2025
c709059
fix
mvadari Dec 17, 2025
a64ffe3
try again
mvadari Dec 17, 2025
cec3031
try one more time
mvadari Dec 17, 2025
86ffea7
fingers crossed
mvadari Dec 17, 2025
1662244
new error
mvadari Dec 17, 2025
19bf175
add comments
mvadari Dec 17, 2025
bf74962
switch to 3 years for testing
mvadari Dec 22, 2025
90f7826
test (fingers crossed...)
mvadari Dec 22, 2025
514175e
fix permissions
mvadari Dec 22, 2025
cea9b55
try again
mvadari Dec 22, 2025
b698163
fix secret name
mvadari Dec 22, 2025
8635770
debug
mvadari Dec 23, 2025
7fff2cf
try again
mvadari Dec 23, 2025
29cae03
try again
mvadari Dec 23, 2025
8ca726f
add dry run
mvadari Dec 23, 2025
ecf3b23
try the app
mvadari Dec 23, 2025
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
142 changes: 142 additions & 0 deletions .github/scripts/process-stale-discussions.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
#!/bin/bash
set -e

# This script processes stale discussions in a GitHub repository
# It warns discussions that haven't been updated in STALE_DAYS
# It closes discussions that were warned WARNING_DAYS ago with no activity

# Environment variables expected:
# - STALE_DAYS: Number of days without updates to consider a discussion stale
# - WARNING_DAYS: Number of days to wait after warning before closing
# - WARNING_MESSAGE: Message to post when warning
# - CLOSE_MESSAGE: Message to post when closing
# - GITHUB_REPOSITORY_OWNER: Owner of the repository
# - GITHUB_REPOSITORY_NAME: Name of the repository
# - GH_TOKEN: GitHub token for API access
# - DRY_RUN: Set to "true" to only print what would happen without making changes (optional)

# Calculate cutoff dates
SECONDS_IN_DAY=86400
STALE_CUTOFF=$(date -u -d "@$(($(date +%s) - STALE_DAYS * SECONDS_IN_DAY))" '+%Y-%m-%dT%H:%M:%SZ')
CLOSE_CUTOFF=$(date -u -d "@$(($(date +%s) - (STALE_DAYS + WARNING_DAYS) * SECONDS_IN_DAY))" '+%Y-%m-%dT%H:%M:%SZ')

echo "Stale cutoff (for warnings): $STALE_CUTOFF"
echo "Close cutoff (for closing): $CLOSE_CUTOFF"

if [ "$DRY_RUN" = "true" ]; then
echo ""
echo "*** DRY RUN MODE - No actual changes will be made ***"
fi

# Debug: Check token permissions
echo ""
echo "Checking GitHub token permissions..."
gh api /repos/$GITHUB_REPOSITORY_OWNER/$GITHUB_REPOSITORY_NAME --jq '.permissions' || echo "Could not fetch repo permissions"

# Fetch discussions using GitHub GraphQL API
gh api graphql -f query='
query($owner: String!, $repo: String!, $cursor: String) {
repository(owner: $owner, name: $repo) {
discussions(first: 100, after: $cursor, orderBy: {field: UPDATED_AT, direction: ASC}) {
pageInfo {
hasNextPage
endCursor
}
nodes {
id
number
title
url
createdAt
updatedAt
closed
locked
comments(last: 10) {
nodes {
body
createdAt
author {
login
}
}
}
}
}
}
}
' -f owner="$GITHUB_REPOSITORY_OWNER" -f repo="$GITHUB_REPOSITORY_NAME" > discussions.json

# Process discussions to close
# A discussion should be closed if:
# 1. It has a warning comment containing "will be closed in 30 days"
# 2. That warning comment is older than WARNING_DAYS
# 3. The discussion hasn't been updated since the warning (or updates are also old)
echo ""
echo "=== Discussions to close - warned ${WARNING_DAYS}+ days ago with no activity ==="
cat discussions.json | jq -r --arg warningCutoff "$CLOSE_CUTOFF" '.data.repository.discussions.nodes[] | select(.closed == false) | . as $discussion | ((.comments.nodes // []) | map(select(.body | contains("will be closed in 30 days"))) | last) as $warningComment | select($warningComment != null) | select($warningComment.createdAt < $warningCutoff) | select($discussion.updatedAt <= $warningComment.createdAt or $discussion.updatedAt < $warningCutoff) | @json' | while IFS= read -r discussion; do
if [ -n "$discussion" ]; then
DISCUSSION_ID=$(echo "$discussion" | jq -r '.id')
DISCUSSION_NUMBER=$(echo "$discussion" | jq -r '.number')
DISCUSSION_TITLE=$(echo "$discussion" | jq -r '.title')
DISCUSSION_URL=$(echo "$discussion" | jq -r '.url')
DISCUSSION_UPDATED=$(echo "$discussion" | jq -r '.updatedAt')

echo "Discussion #$DISCUSSION_NUMBER: $DISCUSSION_TITLE"
echo " URL: $DISCUSSION_URL"
echo " Last updated: $DISCUSSION_UPDATED"
if [ "$DRY_RUN" = "true" ]; then
echo " Action: Would close and lock (DRY RUN)"
else
echo " Action: Closing and locking"

# Step 1: Add a closing comment explaining why the discussion was closed
echo " Adding close comment..."
gh api graphql -f query='mutation($discussionId: ID!, $body: String!) { addDiscussionComment(input: {discussionId: $discussionId, body: $body}) { comment { id } } }' -f discussionId="$DISCUSSION_ID" -f body="$CLOSE_MESSAGE"

# Step 2: Close the discussion
echo " Closing discussion..."
gh api graphql -f query='mutation($discussionId: ID!) { closeDiscussion(input: {discussionId: $discussionId}) { discussion { id } } }' -f discussionId="$DISCUSSION_ID"

# Step 3: Lock the discussion to prevent further comments
echo " Locking discussion..."
gh api graphql -f query='mutation($discussionId: ID!) { lockLockable(input: {lockableId: $discussionId}) { lockedRecord { locked } } }' -f discussionId="$DISCUSSION_ID"
fi

echo ""
fi
done

# Process discussions to warn
# A discussion should be warned if:
# 1. It hasn't been updated in STALE_DAYS
# 2. Either:
# a. It doesn't have a warning comment yet, OR
# b. It has a warning but was updated after that warning (user responded, so we warn again)
echo ""
echo "=== Discussions to warn - stale for ${STALE_DAYS}+ days, not yet warned ==="
cat discussions.json | jq -r --arg staleCutoff "$STALE_CUTOFF" '.data.repository.discussions.nodes[] | select(.closed == false) | select(.updatedAt < $staleCutoff) | . as $discussion | ((.comments.nodes // []) | map(select(.body | contains("will be closed in 30 days"))) | last) as $warningComment | select($warningComment == null or $discussion.updatedAt > $warningComment.createdAt) | @json' | while IFS= read -r discussion; do
if [ -n "$discussion" ]; then
DISCUSSION_ID=$(echo "$discussion" | jq -r '.id')
DISCUSSION_NUMBER=$(echo "$discussion" | jq -r '.number')
DISCUSSION_TITLE=$(echo "$discussion" | jq -r '.title')
DISCUSSION_URL=$(echo "$discussion" | jq -r '.url')
DISCUSSION_UPDATED=$(echo "$discussion" | jq -r '.updatedAt')

echo "Discussion #$DISCUSSION_NUMBER: $DISCUSSION_TITLE"
echo " URL: $DISCUSSION_URL"
echo " Last updated: $DISCUSSION_UPDATED"
if [ "$DRY_RUN" = "true" ]; then
echo " Action: Would add warning comment (DRY RUN)"
else
echo " Action: Adding warning comment"

# Add a warning comment to the discussion
echo " Adding warning comment..."
gh api graphql -f query='mutation($discussionId: ID!, $body: String!) { addDiscussionComment(input: {discussionId: $discussionId, body: $body}) { comment { id } } }' -f discussionId="$DISCUSSION_ID" -f body="$WARNING_MESSAGE"
fi

echo ""
fi
done

echo "Done!"
57 changes: 57 additions & 0 deletions .github/workflows/discussions.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Stale GitHub Discussions

on:
pull_request: # just for debug purposes
workflow_dispatch:
schedule:
- cron: "0 6 * * 1" # Every Monday at 6:00 AM UTC (8:00 AM CET)

permissions:
discussions: write # Required to comment, close, and lock discussions
contents: read # Required to checkout the repository

env:
STALE_DAYS: 1095 # 365 * 3 # Number of days without updates to consider a discussion stale
WARNING_DAYS: 30 # Number of days to wait after warning before closing
WARNING_MESSAGE: |
This discussion has not been updated in over a year and will be closed in 30 days if there is no further activity.

If you would like to continue this discussion, please add a comment. Otherwise, this discussion will be automatically closed and locked.
CLOSE_MESSAGE: |
This discussion has been automatically closed and locked due to inactivity.

If you would like to reopen this discussion, please reach out to the repository editors.

jobs:
run:
runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'XRPLF' }}

steps:
- name: Check out code
uses: actions/checkout@v5

- name: Generate token from GitHub App
id: generate_token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}

- name: Fetch and process stale discussions
env:
GH_TOKEN: ${{ steps.generate_token.outputs.token }}
STALE_DAYS: ${{ env.STALE_DAYS }}
WARNING_DAYS: ${{ env.WARNING_DAYS }}
WARNING_MESSAGE: ${{ env.WARNING_MESSAGE }}
CLOSE_MESSAGE: ${{ env.CLOSE_MESSAGE }}
GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }}
GITHUB_REPOSITORY_NAME: ${{ github.event.repository.name }}
run: |
if [ -z "$GH_TOKEN" ]; then
echo "ERROR: GH_TOKEN is not set. Make sure the GitHub App is configured correctly."
exit 1
fi
echo "GH_TOKEN is set (length: ${#GH_TOKEN})"
chmod +x .github/scripts/process-stale-discussions.sh
.github/scripts/process-stale-discussions.sh