Skip to content

Close Incomplete Issues #102

Close Incomplete Issues

Close Incomplete Issues #102

name: Close Incomplete Issues
on:
schedule:
- cron: '0 10 * * *' # daily at 10:00 UTC
workflow_dispatch: {}
permissions:
issues: write
jobs:
close-incomplete:
runs-on: ubuntu-latest
steps:
- name: Warn and close issues still awaiting required info
uses: actions/github-script@v7
with:
script: |
const WARN_AFTER_DAYS = 2;
const CLOSE_AFTER_DAYS = 5;
const LABEL = 'more info required';
const now = Date.now();
const warnCutoff = new Date(now - WARN_AFTER_DAYS * 86400_000);
const closeCutoff = new Date(now - CLOSE_AFTER_DAYS * 86400_000);
const issues = await github.paginate(github.rest.issues.listForRepo, {
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
labels: LABEL,
per_page: 100,
});
console.log(`Found ${issues.length} open issue(s) with "${LABEL}" label.`);
for (const issue of issues) {
if (issue.pull_request) continue;
const comments = await github.paginate(github.rest.issues.listComments, {
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
per_page: 100,
});
// Clock starts when the bot most-recently flagged the issue as incomplete.
// If the reporter had fixed all problems, the validator would have removed the label;
// its continued presence means the info is still missing.
// The validator re-uses the same bot comment via updateComment when the body is
// re-edited, so updated_at captures the latest re-flag time — using created_at
// here would let pre-flag reporter replies erroneously match reporterRepliesAfterFlag
// and bounce a still-incomplete issue back to maintainer review.
const latestBotFlag = comments.findLast(
c => c.user.type === 'Bot' && c.body != null && c.body.includes('missing that we need before we can investigate')
);
if (!latestBotFlag) {
console.log(`#${issue.number}: no bot validation comment found, skipping.`);
continue;
}
const flaggedAt = new Date(latestBotFlag.updated_at || latestBotFlag.created_at);
console.log(`#${issue.number}: flagged at ${flaggedAt.toISOString()}`);
// Backstop for the comment-trigger handler in issue_validator.yml:
// if the reporter responded via a comment after the bot flag (e.g. they
// posted debug logs as an attachment instead of editing the issue body),
// do not close. Swap to the maintainer-review label so a human triages.
const reporterRepliesAfterFlag = comments.filter(
c => c.user.type !== 'Bot' &&
c.user.login === issue.user.login &&
new Date(c.created_at) > flaggedAt
);
if (reporterRepliesAfterFlag.length > 0) {
console.log(
`#${issue.number}: reporter posted ${reporterRepliesAfterFlag.length} ` +
`reply/replies after the bot flag, marking for maintainer review instead of closing.`
);
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
name: LABEL,
}).catch(e => console.log(`Could not remove ${LABEL}: ${e.message}`));
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
labels: ['awaiting maintainer review'],
});
continue;
}
if (flaggedAt < closeCutoff) {
// ── 5+ days with no fix → close ────────────────────────────────
console.log(`#${issue.number}: past close deadline, closing.`);
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body:
`Closing this issue after **${CLOSE_AFTER_DAYS} days** with no response to the missing-information request above.\n\n` +
`If you still experience this problem, please open a **new issue** and make sure to fill in every required field — ` +
`version, device details, reproduction steps, and debug logs. Without that information we can't investigate. 🙏`,
});
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
state: 'closed',
state_reason: 'not_planned',
});
} else if (flaggedAt < warnCutoff) {
// ── 2–5 days → post a single reminder if not already warned ────
const alreadyWarned = comments.some(
c => c.user.type === 'Bot' && c.body != null && c.body.includes('will be closed in')
);
if (alreadyWarned) {
console.log(`#${issue.number}: reminder already posted, skipping.`);
continue;
}
const daysLeft = CLOSE_AFTER_DAYS - WARN_AFTER_DAYS;
console.log(`#${issue.number}: posting ${WARN_AFTER_DAYS}-day warning.`);
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body:
`👋 @${issue.user.login} — this issue is still waiting for the missing information flagged above. ` +
`It will be closed in **${daysLeft} day${daysLeft !== 1 ? 's' : ''}** if no update is received.\n\n` +
`> **How to update your report:** click the three-dot menu (⋯) at the top-right of your original post → *Edit*, ` +
`then fill in the missing fields and save. The validator will re-check automatically.`,
});
} else {
console.log(`#${issue.number}: flagged less than ${WARN_AFTER_DAYS} days ago, no action yet.`);
}
}