main
yml 108 lines 3.65 KB
Raw
1 name: Close inactive issues and PRs
2
3 on:
4 schedule:
5 - cron: "17 3 * * *"
6 workflow_dispatch:
7 inputs:
8 inactive_days:
9 description: "Close items with no activity for more than N days"
10 required: false
11 default: "90"
12 dry_run:
13 description: "If true, only print URLs (no comment/close)"
14 required: false
15 default: "true"
16
17 permissions:
18 issues: write
19 pull-requests: write
20
21 env:
22 DEFAULT_INACTIVE_DAYS: "90"
23 DEFAULT_DRY_RUN: "false"
24
25 jobs:
26 close_inactive:
27 if: github.repository == 'agent0ai/agent-zero' && github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
28 runs-on: ubuntu-latest
29 steps:
30 - name: Find and optionally close inactive issues/PRs
31 uses: actions/github-script@v7
32 env:
33 INACTIVE_DAYS: ${{ github.event_name == 'workflow_dispatch' && inputs.inactive_days || env.DEFAULT_INACTIVE_DAYS }}
34 DRY_RUN: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run || env.DEFAULT_DRY_RUN }}
35 with:
36 script: |
37 const inactiveDaysRaw = process.env.INACTIVE_DAYS ?? "90";
38 const inactiveDays = Number.parseInt(inactiveDaysRaw, 10);
39 if (!Number.isFinite(inactiveDays) || inactiveDays <= 0) {
40 core.setFailed(`Invalid INACTIVE_DAYS: ${inactiveDaysRaw}`);
41 return;
42 }
43
44 const dryRunRaw = (process.env.DRY_RUN ?? "true").toLowerCase();
45 const dryRun = ["1", "true", "yes", "y"].includes(dryRunRaw);
46
47 const now = new Date();
48 const cutoff = new Date(now.getTime() - inactiveDays * 24 * 60 * 60 * 1000);
49 const cutoffDate = cutoff.toISOString().slice(0, 10);
50
51 core.info(`inactiveDays=${inactiveDays}`);
52 core.info(`dryRun=${dryRun}`);
53 core.info(`cutoffDate=${cutoffDate}`);
54
55 const owner = context.repo.owner;
56 const repo = context.repo.repo;
57
58 async function processQuery(kind, searchQuery) {
59 core.info(`Searching ${kind}: ${searchQuery}`);
60
61 const items = await github.paginate(github.rest.search.issuesAndPullRequests, {
62 q: searchQuery,
63 per_page: 100,
64 });
65
66 if (items.length === 0) {
67 core.info(`No inactive ${kind} found.`);
68 return;
69 }
70
71 core.info(`Found ${items.length} inactive ${kind}. URLs:`);
72 for (const item of items) {
73 core.info(item.html_url);
74 }
75
76 if (dryRun) {
77 return;
78 }
79
80 for (const item of items) {
81 const issueNumber = item.number;
82 const url = item.html_url;
83
84 try {
85 await github.rest.issues.createComment({
86 owner,
87 repo,
88 issue_number: issueNumber,
89 body: `Closing due to inactivity of ${inactiveDays} days.`,
90 });
91
92 await github.rest.issues.update({
93 owner,
94 repo,
95 issue_number: issueNumber,
96 state: "closed",
97 });
98
99 core.info(`Closed: ${url}`);
100 } catch (err) {
101 core.warning(`Failed to close ${url}: ${err?.message ?? String(err)}`);
102 }
103 }
104 }
105
106 const base = `repo:${owner}/${repo} is:open updated:<${cutoffDate} sort:updated-asc`;
107 await processQuery("issues", `${base} is:issue`);
108 await processQuery("pull requests", `${base} is:pr`);