mirror of
https://github.com/anthropics/claude-code.git
synced 2025-10-19 03:17:50 +03:00
- Add stale-issue-manager.yml to check and manage inactive issues daily - Add remove-autoclose-label.yml to remove autoclose label when users comment - Issues get warning after 30 days of inactivity - Issues auto-close after 60 days (30 days after warning) - User comments reset the stale timer by removing autoclose label 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
158 lines
6.3 KiB
YAML
158 lines
6.3 KiB
YAML
name: "Manage Stale Issues"
|
|
|
|
on:
|
|
schedule:
|
|
# 2am Pacific = 9am UTC (10am UTC during DST)
|
|
- cron: "0 10 * * *"
|
|
workflow_dispatch:
|
|
|
|
permissions:
|
|
issues: write
|
|
|
|
concurrency:
|
|
group: stale-issue-manager
|
|
|
|
jobs:
|
|
manage-stale-issues:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Manage stale issues
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
const oneMonthAgo = new Date();
|
|
oneMonthAgo.setDate(oneMonthAgo.getDate() - 30);
|
|
|
|
const twoMonthsAgo = new Date();
|
|
twoMonthsAgo.setDate(twoMonthsAgo.getDate() - 60);
|
|
|
|
const warningComment = `This issue has been inactive for 30 days. If the issue is still occurring, please comment to let us know. Otherwise, this issue will be automatically closed in 30 days for housekeeping purposes.`;
|
|
|
|
const closingComment = `This issue has been automatically closed due to 60 days of inactivity. If you're still experiencing this issue, please open a new issue with updated information.`;
|
|
|
|
let page = 1;
|
|
let hasMore = true;
|
|
let totalWarned = 0;
|
|
let totalClosed = 0;
|
|
let totalLabeled = 0;
|
|
|
|
while (hasMore) {
|
|
// Get open issues sorted by last updated (oldest first)
|
|
const { data: issues } = await github.rest.issues.listForRepo({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
state: 'open',
|
|
sort: 'updated',
|
|
direction: 'asc',
|
|
per_page: 100,
|
|
page: page
|
|
});
|
|
|
|
if (issues.length === 0) {
|
|
hasMore = false;
|
|
break;
|
|
}
|
|
|
|
for (const issue of issues) {
|
|
// Skip if already locked
|
|
if (issue.locked) continue;
|
|
|
|
// Skip pull requests
|
|
if (issue.pull_request) continue;
|
|
|
|
// Check if updated more recently than 30 days ago
|
|
const updatedAt = new Date(issue.updated_at);
|
|
if (updatedAt > oneMonthAgo) {
|
|
// Since issues are sorted by updated_at ascending,
|
|
// once we hit a recent issue, all remaining will be recent too
|
|
hasMore = false;
|
|
break;
|
|
}
|
|
|
|
// Check if issue has autoclose label
|
|
const hasAutocloseLabel = issue.labels.some(label =>
|
|
typeof label === 'object' && label.name === 'autoclose'
|
|
);
|
|
|
|
try {
|
|
// Get comments to check for existing warning
|
|
const { data: comments } = await github.rest.issues.listComments({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: issue.number,
|
|
per_page: 100
|
|
});
|
|
|
|
// Find the last comment from github-actions bot
|
|
const botComments = comments.filter(comment =>
|
|
comment.user && comment.user.login === 'github-actions[bot]' &&
|
|
comment.body && comment.body.includes('inactive for 30 days')
|
|
);
|
|
|
|
const lastBotComment = botComments[botComments.length - 1];
|
|
|
|
if (lastBotComment) {
|
|
// Check if the bot comment is older than 30 days (total 60 days of inactivity)
|
|
const botCommentDate = new Date(lastBotComment.created_at);
|
|
if (botCommentDate < oneMonthAgo) {
|
|
// Close the issue - it's been stale for 60+ days
|
|
console.log(`Closing issue #${issue.number} (stale for 60+ days): ${issue.title}`);
|
|
|
|
// Post closing comment
|
|
await github.rest.issues.createComment({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: issue.number,
|
|
body: closingComment
|
|
});
|
|
|
|
// Close the issue
|
|
await github.rest.issues.update({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: issue.number,
|
|
state: 'closed',
|
|
state_reason: 'not_planned'
|
|
});
|
|
|
|
totalClosed++;
|
|
}
|
|
// If bot comment exists but is recent, issue already has warning
|
|
} else if (updatedAt < oneMonthAgo) {
|
|
// No bot warning yet, issue is 30+ days old
|
|
console.log(`Warning issue #${issue.number} (stale for 30+ days): ${issue.title}`);
|
|
|
|
// Post warning comment
|
|
await github.rest.issues.createComment({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: issue.number,
|
|
body: warningComment
|
|
});
|
|
|
|
totalWarned++;
|
|
|
|
// Add autoclose label if not present
|
|
if (!hasAutocloseLabel) {
|
|
await github.rest.issues.addLabels({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: issue.number,
|
|
labels: ['autoclose']
|
|
});
|
|
totalLabeled++;
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error(`Failed to process issue #${issue.number}: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
page++;
|
|
}
|
|
|
|
console.log(`Summary:`);
|
|
console.log(`- Issues warned (30 days stale): ${totalWarned}`);
|
|
console.log(`- Issues labeled with autoclose: ${totalLabeled}`);
|
|
console.log(`- Issues closed (60 days stale): ${totalClosed}`);
|