Skip to content

💤 Auto Unassign Inactive Issues #201

💤 Auto Unassign Inactive Issues

💤 Auto Unassign Inactive Issues #201

name: 💤 Auto Unassign Inactive Issues
on:
schedule:
- cron: '0 */12 * * *'
workflow_dispatch:
permissions:
issues: write
pull-requests: read
contents: read
jobs:
auto-unassign:
runs-on: ubuntu-latest
steps:
- name: Auto unassign inactive contributors
uses: actions/github-script@v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const INACTIVE_DAYS = 3;
const now = new Date();
const cutoff = new Date(now.getTime() - INACTIVE_DAYS * 24 * 60 * 60 * 1000);
const issues = await github.paginate(
github.rest.issues.listForRepo,
{ owner, repo, state: 'open', assignee: '*', per_page: 100 }
);
// Fetch open PRs once outside the loop
const openPRs = await github.paginate(
github.rest.pulls.list,
{ owner, repo, state: 'open', per_page: 100 }
);
for (const issue of issues) {
if (issue.pull_request) continue; // skip PRs
// Allow the script to fetch comments to accurately check per-assignee activity
const comments = await github.paginate(
github.rest.issues.listComments,
{ owner, repo, issue_number: issue.number, per_page: 100 }
);
const events = await github.paginate(
github.rest.issues.listEventsForTimeline,
{ owner, repo, issue_number: issue.number, per_page: 100 }
);
const assignees = (issue.assignees || []).map(a => a.login);
for (const assignee of assignees) {
const assignedEvent = events
.filter(e => e.event === 'assigned' && e.assignee && e.assignee.login === assignee)
.sort((a, b) => new Date(b.created_at) - new Date(a.created_at))[0];
if (!assignedEvent) continue;
const assignedAt = new Date(assignedEvent.created_at);
if (assignedAt > cutoff) continue;
// Check for activity after assignment
const contributorComments = comments.filter(c =>
c.user && c.user.login === assignee && new Date(c.created_at) > assignedAt
);
const prs = openPRs;
const linkedPR = prs.find(pr => {
const body = (pr.body || '').toLowerCase();
return pr.user && pr.user.login === assignee && (
body.includes(`#${issue.number}`) ||
body.includes(`closes #${issue.number}`) ||
body.includes(`fixes #${issue.number}`)
);
});
if (contributorComments.length > 0 || linkedPR) continue;
await github.rest.issues.removeAssignees({
owner, repo, issue_number: issue.number, assignees: [assignee]
});
await github.rest.issues.createComment({
owner, repo, issue_number: issue.number,
body: `⚠️ Auto-unassigned @${assignee} due to inactivity.\n\n` +
`Per the contribution guidelines, assigned contributors are expected to show progress within ${INACTIVE_DAYS} days.\n\n` +
`You may re-request assignment later if you intend to continue working on this issue.`
});
}
}