add stale discussion check #27
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) | |
| env: | |
| STALE_DAYS: 365 # 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: Fetch and process stale discussions | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| # Calculate cutoff dates | |
| SECONDS_IN_DAY=86400 | |
| STALE_CUTOFF=$(date -u -d "@$(($(date +%s) - ${{ env.STALE_DAYS }} * SECONDS_IN_DAY))" '+%Y-%m-%dT%H:%M:%SZ') | |
| CLOSE_CUTOFF=$(date -u -d "@$(($(date +%s) - (${{ env.STALE_DAYS }} + ${{ env.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" | |
| # 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.event.repository.name }}" > discussions.json | |
| # Process discussions to close (stale + warning period passed) | |
| # A discussion should be closed if it has a warning comment from github-actions bot | |
| # that is older than WARNING_DAYS | |
| echo "" | |
| echo "=== Discussions to close (warned ${{ env.WARNING_DAYS }}+ days ago with no activity) ===" | |
| 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 | |
| ' discussions.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" | |
| echo " Action: Would close and lock" | |
| # COMMENTED OUT FOR TESTING | |
| # 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="${{ env.CLOSE_MESSAGE }}" | |
| # echo " Closing discussion..." | |
| # gh api graphql -f query=' | |
| # mutation($discussionId: ID!) { | |
| # closeDiscussion(input: {discussionId: $discussionId}) { | |
| # discussion { id } | |
| # } | |
| # } | |
| # ' -f discussionId="$DISCUSSION_ID" | |
| # echo " Locking discussion..." | |
| # gh api graphql -f query=' | |
| # mutation($discussionId: ID!) { | |
| # lockLockable(input: {lockableId: $discussionId}) { | |
| # lockedRecord { locked } | |
| # } | |
| # } | |
| # ' -f discussionId="$DISCUSSION_ID" | |
| echo "" | |
| fi | |
| done | |
| # Process discussions to warn (stale but not yet warned) | |
| # A discussion should be warned if: | |
| # 1. It hasn't been updated in STALE_DAYS | |
| # 2. It doesn't already have a warning comment | |
| # 3. OR it has a warning comment but has been updated since then (meaning user responded) | |
| echo "" | |
| echo "=== Discussions to warn (stale for ${{ env.STALE_DAYS }}+ days, not yet warned) ===" | |
| jq -r --arg staleCutoff "$STALE_CUTOFF" ' | |
| .data.repository.discussions.nodes[] | | |
| select(.closed == false) | | |
| # Check if discussion is stale | |
| select(.updatedAt < $staleCutoff) | | |
| # Find any existing warning comment | |
| . as $discussion | | |
| (.comments.nodes | map(select(.body | contains("will be closed in 30 days"))) | last) as $warningComment | | |
| # Only warn if: no warning exists OR discussion was updated after the last warning | |
| select($warningComment == null or $discussion.updatedAt > $warningComment.createdAt) | | |
| @json | |
| ' discussions.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" | |
| echo " Action: Would add warning comment" | |
| # COMMENTED OUT FOR TESTING | |
| # 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="${{ env.WARNING_MESSAGE }}" | |
| echo "" | |
| fi | |
| done |