From bfdccacb21e046ff20feb85356ef565cceca1b58 Mon Sep 17 00:00:00 2001 From: Mason Daugherty Date: Fri, 13 Mar 2026 16:29:15 -0400 Subject: [PATCH] ci: auto-close needs-title issues after 7 days (#3115) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Completes the `needs-title` issue lifecycle. The existing `check-default-title` workflow labels issues with default/placeholder titles, but there was no automated cleanup — labeled issues sat open indefinitely. This adds a weekly scheduled workflow to close them after 7 days, plus a fix to the existing workflow so the label gets removed when an author updates their title. ## Changes - Add `close-needs-title.yml` scheduled workflow (Monday 9 AM UTC + manual dispatch) that paginates all open issues labeled `needs-title`, skips PRs and issues younger than 7 days, then comments and closes as `not_planned` - Fix the `check-default-title` workflow's early-return path: when a title is edited to be descriptive, call `issues.removeLabel` to strip `needs-title` — previously the label stuck around permanently --- .github/workflows/close-needs-title.yml | 66 +++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 .github/workflows/close-needs-title.yml diff --git a/.github/workflows/close-needs-title.yml b/.github/workflows/close-needs-title.yml new file mode 100644 index 000000000..a0cf8b3c7 --- /dev/null +++ b/.github/workflows/close-needs-title.yml @@ -0,0 +1,66 @@ +--- +name: Close stale needs-title issues + +on: + schedule: + - cron: "0 9 * * 1" # Every Monday at 9 AM UTC + workflow_dispatch: + +jobs: + close-stale: + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + issues: write + + steps: + - name: Close issues with needs-title label older than 7 days + uses: actions/github-script@v8 + with: + script: | + const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000); + + // Paginate through all open issues with needs-title label + const issues = await github.paginate(github.rest.issues.listForRepo, { + owner: context.repo.owner, + repo: context.repo.repo, + labels: 'needs-title', + state: 'open', + per_page: 100, + }); + + let closed = 0; + for (const issue of issues) { + // Skip pull requests (they also appear in the issues API) + if (issue.pull_request) continue; + + const createdAt = new Date(issue.created_at); + if (createdAt >= sevenDaysAgo) { + console.log(`Skipping #${issue.number} — created ${issue.created_at}, not yet 7 days old`); + continue; + } + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + body: [ + 'Closing this issue because the title was not updated within 7 days.', + '', + 'If you still need help, please open a new issue with a descriptive title so maintainers can understand and prioritize it.', + ].join('\n'), + }); + + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + state: 'closed', + state_reason: 'not_planned', + }); + + console.log(`Closed #${issue.number} (created ${issue.created_at})`); + closed++; + } + + console.log(`Done. Closed ${closed} issue(s).`);