name: "Lock Stale Issues" on: schedule: # 8am Pacific = 1pm UTC (2pm UTC during DST) - cron: "0 14 * * *" workflow_dispatch: permissions: issues: write concurrency: group: lock-threads jobs: lock-closed-issues: runs-on: ubuntu-latest steps: - name: Lock closed issues after 7 days of inactivity uses: actions/github-script@v7 with: script: | const sevenDaysAgo = new Date(); sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7); const cutoff = sevenDaysAgo.toISOString().split('T')[0]; const lockComment = `This issue has been automatically locked since it was closed and has not had any activity for 7 days. If you're experiencing a similar issue, please file a new issue and reference this one if it's relevant.`; const query = `repo:${context.repo.owner}/${context.repo.repo} is:issue is:closed is:unlocked updated:<${cutoff}`; console.log(`Search query: ${query}`); const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); const MAX_PER_RUN = 250; const processed = new Set(); let totalLocked = 0; while (totalLocked < MAX_PER_RUN) { const { data } = await github.rest.search.issuesAndPullRequests({ q: query, sort: 'updated', order: 'asc', per_page: 100, }); if (totalLocked === 0) { console.log(`Total candidates: ${data.total_count}`); } const fresh = data.items.filter((i) => !processed.has(i.number)); if (fresh.length === 0) break; for (const issue of fresh) { if (totalLocked >= MAX_PER_RUN) break; processed.add(issue.number); try { await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: issue.number, body: lockComment, }); await github.rest.issues.lock({ owner: context.repo.owner, repo: context.repo.repo, issue_number: issue.number, lock_reason: 'resolved', }); totalLocked++; console.log(`Locked issue #${issue.number}: ${issue.title}`); await sleep(1000); } catch (error) { console.error(`Failed to lock issue #${issue.number}: ${error.message}`); } } } console.log(`Total issues locked: ${totalLocked}`);