From f3026db2e5c254fef81c03df20875442f4808e14 Mon Sep 17 00:00:00 2001 From: galz10 Date: Tue, 2 Dec 2025 13:21:01 -0800 Subject: [PATCH 01/23] feat: add scheduled workflow to close stale issues Adds a new GitHub Action `gemini-scheduled-stale-issue-closer.yml` to automatically manage the issue backlog. - Runs weekly on Sundays. - Closes issues created > 3 months ago with no updates in the last 10 days and < 5 reactions. - Adds a friendly comment before closing. - Includes a `workflow_dispatch` trigger with a `dry_run` option for safe testing. - Currently limits processing to the oldest 100 open issues to ensure safe incremental cleanup. --- .../gemini-scheduled-stale-issue-closer.yml | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 .github/workflows/gemini-scheduled-stale-issue-closer.yml diff --git a/.github/workflows/gemini-scheduled-stale-issue-closer.yml b/.github/workflows/gemini-scheduled-stale-issue-closer.yml new file mode 100644 index 00000000000..3ff3829fe78 --- /dev/null +++ b/.github/workflows/gemini-scheduled-stale-issue-closer.yml @@ -0,0 +1,114 @@ +name: '🔒 Gemini Scheduled Stale Issue Closer' + +on: + schedule: + - cron: '0 0 * * 0' # Every Sunday at midnight UTC + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no changes applied)' + required: false + default: false + type: boolean + +concurrency: + group: '${{ github.workflow }}' + cancel-in-progress: true + +defaults: + run: + shell: 'bash' + +jobs: + close-stale-issues: + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Generate GitHub App Token + id: generate_token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.APP_ID }} + private-key: ${{ secrets.PRIVATE_KEY }} + + - name: Process Stale Issues + uses: actions/github-script@v7 + env: + DRY_RUN: ${{ inputs.dry_run }} + with: + github-token: ${{ steps.generate_token.outputs.token }} + script: | + const dryRun = process.env.DRY_RUN === 'true'; + if (dryRun) { + core.info('DRY RUN MODE ENABLED: No changes will be applied.'); + } + + // 1. Setup Date Thresholds + const threeMonthsAgo = new Date(); + threeMonthsAgo.setMonth(threeMonthsAgo.getMonth() - 3); + + const tenDaysAgo = new Date(); + tenDaysAgo.setDate(tenDaysAgo.getDate() - 10); + + core.info(`Cutoff date for creation (3 months ago): ${threeMonthsAgo.toISOString()}`); + core.info(`Cutoff date for updates (10 days ago): ${tenDaysAgo.toISOString()}`); + + // 2. Fetch all open issues (Limited to 100 for debugging) + const opts = github.rest.issues.listForRepo.endpoint.merge({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + sort: 'created', + direction: 'asc', + per_page: 100 + }); + + // Using listForRepo directly instead of paginate to limit to first 100 results + const response = await github.rest.issues.listForRepo(opts); + const issues = response.data; + core.info(`Found ${issues.length} open issues to check (limit 100).`); + + for (const issue of issues) { + // Safety Check: Skip Pull Requests (GitHub API treats PRs as Issues in list endpoints) + if (issue.pull_request) continue; + + const createdAt = new Date(issue.created_at); + const updatedAt = new Date(issue.updated_at); + const reactionCount = issue.reactions.total_count; + + // 3. Evaluate Conditions + const isOld = createdAt < threeMonthsAgo; + const isStale = updatedAt < tenDaysAgo; + const isUnpopular = reactionCount < 5; + + if (isOld && isStale && isUnpopular) { + const message = `Closing stale issue #${issue.number}: "${issue.title}" (Created: ${createdAt.toISOString()}, Reactions: ${reactionCount})`; + + if (dryRun) { + core.info(`[DRY RUN] ${message}`); + continue; + } + + core.info(message); + + // 4. Comment + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + body: "Hello! As part of our effort to keep our backlog manageable and focus on the most active issues, we are tidying up older reports.\n\nIt looks like this issue hasn't been active for a while, so we are closing it for now. However, if you are still experiencing this bug on the latest stable build, please feel free to reopen this issue or create a new one with updated details.\n\nThank you for your contribution!" + }); + + // 5. Close + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + state: 'closed' + }); + } + } \ No newline at end of file From e1f47cf7878fd66efb0d8981651b77b971a05342 Mon Sep 17 00:00:00 2001 From: galz10 Date: Tue, 2 Dec 2025 14:12:22 -0800 Subject: [PATCH 02/23] fix: lint errors in workflow --- .../gemini-scheduled-stale-issue-closer.yml | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/workflows/gemini-scheduled-stale-issue-closer.yml b/.github/workflows/gemini-scheduled-stale-issue-closer.yml index 3ff3829fe78..109161a05ad 100644 --- a/.github/workflows/gemini-scheduled-stale-issue-closer.yml +++ b/.github/workflows/gemini-scheduled-stale-issue-closer.yml @@ -9,7 +9,7 @@ on: description: 'Run in dry-run mode (no changes applied)' required: false default: false - type: boolean + type: 'boolean' concurrency: group: '${{ github.workflow }}' @@ -21,26 +21,26 @@ defaults: jobs: close-stale-issues: - runs-on: ubuntu-latest + runs-on: 'ubuntu-latest' permissions: - issues: write + issues: 'write' steps: - - name: Checkout - uses: actions/checkout@v4 + - name: 'Checkout' + uses: 'actions/checkout@v4' - - name: Generate GitHub App Token - id: generate_token - uses: actions/create-github-app-token@v1 + - name: 'Generate GitHub App Token' + id: 'generate_token' + uses: 'actions/create-github-app-token@v1' with: - app-id: ${{ secrets.APP_ID }} - private-key: ${{ secrets.PRIVATE_KEY }} + app-id: '${{ secrets.APP_ID }}' + private-key: '${{ secrets.PRIVATE_KEY }}' - - name: Process Stale Issues - uses: actions/github-script@v7 + - name: 'Process Stale Issues' + uses: 'actions/github-script@v7' env: - DRY_RUN: ${{ inputs.dry_run }} + DRY_RUN: '${{ inputs.dry_run }}' with: - github-token: ${{ steps.generate_token.outputs.token }} + github-token: '${{ steps.generate_token.outputs.token }}'' script: | const dryRun = process.env.DRY_RUN === 'true'; if (dryRun) { @@ -50,7 +50,7 @@ jobs: // 1. Setup Date Thresholds const threeMonthsAgo = new Date(); threeMonthsAgo.setMonth(threeMonthsAgo.getMonth() - 3); - + const tenDaysAgo = new Date(); tenDaysAgo.setDate(tenDaysAgo.getDate() - 10); @@ -66,7 +66,7 @@ jobs: direction: 'asc', per_page: 100 }); - + // Using listForRepo directly instead of paginate to limit to first 100 results const response = await github.rest.issues.listForRepo(opts); const issues = response.data; @@ -87,14 +87,14 @@ jobs: if (isOld && isStale && isUnpopular) { const message = `Closing stale issue #${issue.number}: "${issue.title}" (Created: ${createdAt.toISOString()}, Reactions: ${reactionCount})`; - + if (dryRun) { core.info(`[DRY RUN] ${message}`); continue; } core.info(message); - + // 4. Comment await github.rest.issues.createComment({ owner: context.repo.owner, @@ -111,4 +111,4 @@ jobs: state: 'closed' }); } - } \ No newline at end of file + } From 06d77cc4957e3b20161db49120cac4a9ab0f7191 Mon Sep 17 00:00:00 2001 From: galz10 Date: Tue, 2 Dec 2025 14:33:19 -0800 Subject: [PATCH 03/23] Add temporary push trigger for testing --- .github/workflows/gemini-scheduled-stale-issue-closer.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/gemini-scheduled-stale-issue-closer.yml b/.github/workflows/gemini-scheduled-stale-issue-closer.yml index 109161a05ad..9eab17e8601 100644 --- a/.github/workflows/gemini-scheduled-stale-issue-closer.yml +++ b/.github/workflows/gemini-scheduled-stale-issue-closer.yml @@ -10,6 +10,9 @@ on: required: false default: false type: 'boolean' + push: + branches: + - 'galzahavi/add/stale-issue-workflow' concurrency: group: '${{ github.workflow }}' From f420cecb67fdf839adab47f43166d815fb29db6b Mon Sep 17 00:00:00 2001 From: galz10 Date: Tue, 2 Dec 2025 14:35:34 -0800 Subject: [PATCH 04/23] fix: safe dry-run test mode --- .../gemini-scheduled-stale-issue-closer.yml | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/gemini-scheduled-stale-issue-closer.yml b/.github/workflows/gemini-scheduled-stale-issue-closer.yml index 9eab17e8601..8d96b9db70e 100644 --- a/.github/workflows/gemini-scheduled-stale-issue-closer.yml +++ b/.github/workflows/gemini-scheduled-stale-issue-closer.yml @@ -43,7 +43,7 @@ jobs: env: DRY_RUN: '${{ inputs.dry_run }}' with: - github-token: '${{ steps.generate_token.outputs.token }}'' + github-token: '${{ steps.generate_token.outputs.token }}' script: | const dryRun = process.env.DRY_RUN === 'true'; if (dryRun) { @@ -99,19 +99,19 @@ jobs: core.info(message); // 4. Comment - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - body: "Hello! As part of our effort to keep our backlog manageable and focus on the most active issues, we are tidying up older reports.\n\nIt looks like this issue hasn't been active for a while, so we are closing it for now. However, if you are still experiencing this bug on the latest stable build, please feel free to reopen this issue or create a new one with updated details.\n\nThank you for your contribution!" - }); + // await github.rest.issues.createComment({ + // owner: context.repo.owner, + // repo: context.repo.repo, + // issue_number: issue.number, + // body: "Hello! As part of our effort to keep our backlog manageable and focus on the most active issues, we are tidying up older reports.\n\nIt looks like this issue hasn't been active for a while, so we are closing it for now. However, if you are still experiencing this bug on the latest stable build, please feel free to reopen this issue or create a new one with updated details.\n\nThank you for your contribution!" + // }); // 5. Close - await github.rest.issues.update({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - state: 'closed' - }); + // await github.rest.issues.update({ + // owner: context.repo.owner, + // repo: context.repo.repo, + // issue_number: issue.number, + // state: 'closed' + // }); } } From 6ddfa95c1132fa1f1eb2b54ac991102dcb19bdbb Mon Sep 17 00:00:00 2001 From: galz10 Date: Tue, 2 Dec 2025 14:38:13 -0800 Subject: [PATCH 05/23] chore: enable dry-run by default for testing --- .github/workflows/gemini-scheduled-stale-issue-closer.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gemini-scheduled-stale-issue-closer.yml b/.github/workflows/gemini-scheduled-stale-issue-closer.yml index 8d96b9db70e..48e85929cc4 100644 --- a/.github/workflows/gemini-scheduled-stale-issue-closer.yml +++ b/.github/workflows/gemini-scheduled-stale-issue-closer.yml @@ -8,7 +8,7 @@ on: dry_run: description: 'Run in dry-run mode (no changes applied)' required: false - default: false + default: true type: 'boolean' push: branches: From 8f2fd474fdcd0d96561b999035c87e4830fc6074 Mon Sep 17 00:00:00 2001 From: galz10 Date: Tue, 2 Dec 2025 14:56:53 -0800 Subject: [PATCH 06/23] feat: filter by issue type and add summary count --- .../gemini-scheduled-stale-issue-closer.yml | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/gemini-scheduled-stale-issue-closer.yml b/.github/workflows/gemini-scheduled-stale-issue-closer.yml index 48e85929cc4..04d9c2fed2a 100644 --- a/.github/workflows/gemini-scheduled-stale-issue-closer.yml +++ b/.github/workflows/gemini-scheduled-stale-issue-closer.yml @@ -54,11 +54,11 @@ jobs: const threeMonthsAgo = new Date(); threeMonthsAgo.setMonth(threeMonthsAgo.getMonth() - 3); - const tenDaysAgo = new Date(); - tenDaysAgo.setDate(tenDaysAgo.getDate() - 10); + const fourteenDaysAgo = new Date(); + fourteenDaysAgo.setDate(fourteenDaysAgo.getDate() - 14); core.info(`Cutoff date for creation (3 months ago): ${threeMonthsAgo.toISOString()}`); - core.info(`Cutoff date for updates (10 days ago): ${tenDaysAgo.toISOString()}`); + core.info(`Cutoff date for updates (14 days ago): ${fourteenDaysAgo.toISOString()}`); // 2. Fetch all open issues (Limited to 100 for debugging) const opts = github.rest.issues.listForRepo.endpoint.merge({ @@ -88,8 +88,16 @@ jobs: const isStale = updatedAt < tenDaysAgo; const isUnpopular = reactionCount < 5; - if (isOld && isStale && isUnpopular) { + // 4. Check Issue Type (Must be 'Bug' or 'No Type') + // Note: The REST API might return 'type' as null if not set. + // We check if type is 'Bug', explicitly 'No Type', or null/undefined. + const typeName = issue.type ? issue.type.name : null; + const isBugOrUntyped = typeName === 'Bug' || typeName === 'No Type' || !typeName; + + if (isOld && isStale && isUnpopular && isBugOrUntyped) { const message = `Closing stale issue #${issue.number}: "${issue.title}" (Created: ${createdAt.toISOString()}, Reactions: ${reactionCount})`; + + processedCount++; if (dryRun) { core.info(`[DRY RUN] ${message}`); From 0b21009a170e0cae0161fead260b2a7b3980c664 Mon Sep 17 00:00:00 2001 From: galz10 Date: Tue, 2 Dec 2025 14:57:50 -0800 Subject: [PATCH 07/23] fix: variable name reference --- .github/workflows/gemini-scheduled-stale-issue-closer.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/gemini-scheduled-stale-issue-closer.yml b/.github/workflows/gemini-scheduled-stale-issue-closer.yml index 04d9c2fed2a..064d005db3a 100644 --- a/.github/workflows/gemini-scheduled-stale-issue-closer.yml +++ b/.github/workflows/gemini-scheduled-stale-issue-closer.yml @@ -54,11 +54,11 @@ jobs: const threeMonthsAgo = new Date(); threeMonthsAgo.setMonth(threeMonthsAgo.getMonth() - 3); - const fourteenDaysAgo = new Date(); - fourteenDaysAgo.setDate(fourteenDaysAgo.getDate() - 14); + const tenDaysAgo = new Date(); + tenDaysAgo.setDate(tenDaysAgo.getDate() - 10); core.info(`Cutoff date for creation (3 months ago): ${threeMonthsAgo.toISOString()}`); - core.info(`Cutoff date for updates (14 days ago): ${fourteenDaysAgo.toISOString()}`); + core.info(`Cutoff date for updates (10 days ago): ${tenDaysAgo.toISOString()}`); // 2. Fetch all open issues (Limited to 100 for debugging) const opts = github.rest.issues.listForRepo.endpoint.merge({ From 1e28256ff4441d1cbc01ef018db2e8136a5eaaed Mon Sep 17 00:00:00 2001 From: galz10 Date: Tue, 2 Dec 2025 14:58:46 -0800 Subject: [PATCH 08/23] fix: initialize processedCount variable --- .github/workflows/gemini-scheduled-stale-issue-closer.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/gemini-scheduled-stale-issue-closer.yml b/.github/workflows/gemini-scheduled-stale-issue-closer.yml index 064d005db3a..3e2d0beeb9a 100644 --- a/.github/workflows/gemini-scheduled-stale-issue-closer.yml +++ b/.github/workflows/gemini-scheduled-stale-issue-closer.yml @@ -75,6 +75,8 @@ jobs: const issues = response.data; core.info(`Found ${issues.length} open issues to check (limit 100).`); + let processedCount = 0; + for (const issue of issues) { // Safety Check: Skip Pull Requests (GitHub API treats PRs as Issues in list endpoints) if (issue.pull_request) continue; From 15aa41dcc86932574023e89c2644cf10e5fd3440 Mon Sep 17 00:00:00 2001 From: galz10 Date: Tue, 2 Dec 2025 15:01:14 -0800 Subject: [PATCH 09/23] fix: log total processed count --- .github/workflows/gemini-scheduled-stale-issue-closer.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/gemini-scheduled-stale-issue-closer.yml b/.github/workflows/gemini-scheduled-stale-issue-closer.yml index 3e2d0beeb9a..047bd12fe9f 100644 --- a/.github/workflows/gemini-scheduled-stale-issue-closer.yml +++ b/.github/workflows/gemini-scheduled-stale-issue-closer.yml @@ -125,3 +125,9 @@ jobs: // }); } } + + if (dryRun) { + core.info(`[DRY RUN] Total issues that would be closed: ${processedCount}`); + } else { + core.info(`Total issues closed: ${processedCount}`); + } From 0467a40e8cb744a2d56db9fff951f39f0f31f9a8 Mon Sep 17 00:00:00 2001 From: galz10 Date: Tue, 2 Dec 2025 15:06:00 -0800 Subject: [PATCH 10/23] fix: lint errors --- .github/workflows/gemini-scheduled-stale-issue-closer.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gemini-scheduled-stale-issue-closer.yml b/.github/workflows/gemini-scheduled-stale-issue-closer.yml index 047bd12fe9f..dc3ace2d3d3 100644 --- a/.github/workflows/gemini-scheduled-stale-issue-closer.yml +++ b/.github/workflows/gemini-scheduled-stale-issue-closer.yml @@ -98,7 +98,7 @@ jobs: if (isOld && isStale && isUnpopular && isBugOrUntyped) { const message = `Closing stale issue #${issue.number}: "${issue.title}" (Created: ${createdAt.toISOString()}, Reactions: ${reactionCount})`; - + processedCount++; if (dryRun) { @@ -125,7 +125,7 @@ jobs: // }); } } - + if (dryRun) { core.info(`[DRY RUN] Total issues that would be closed: ${processedCount}`); } else { From af4ddb564d437486b98d057aa9a337a673f0ae70 Mon Sep 17 00:00:00 2001 From: galz10 Date: Tue, 2 Dec 2025 15:08:27 -0800 Subject: [PATCH 11/23] feat: add type breakdown and issue links to logs --- .../gemini-scheduled-stale-issue-closer.yml | 90 +++++++++++-------- 1 file changed, 52 insertions(+), 38 deletions(-) diff --git a/.github/workflows/gemini-scheduled-stale-issue-closer.yml b/.github/workflows/gemini-scheduled-stale-issue-closer.yml index dc3ace2d3d3..d2ec105bb61 100644 --- a/.github/workflows/gemini-scheduled-stale-issue-closer.yml +++ b/.github/workflows/gemini-scheduled-stale-issue-closer.yml @@ -70,44 +70,58 @@ jobs: per_page: 100 }); - // Using listForRepo directly instead of paginate to limit to first 100 results - const response = await github.rest.issues.listForRepo(opts); - const issues = response.data; - core.info(`Found ${issues.length} open issues to check (limit 100).`); - - let processedCount = 0; - - for (const issue of issues) { - // Safety Check: Skip Pull Requests (GitHub API treats PRs as Issues in list endpoints) - if (issue.pull_request) continue; - - const createdAt = new Date(issue.created_at); - const updatedAt = new Date(issue.updated_at); - const reactionCount = issue.reactions.total_count; - - // 3. Evaluate Conditions - const isOld = createdAt < threeMonthsAgo; - const isStale = updatedAt < tenDaysAgo; - const isUnpopular = reactionCount < 5; - - // 4. Check Issue Type (Must be 'Bug' or 'No Type') - // Note: The REST API might return 'type' as null if not set. - // We check if type is 'Bug', explicitly 'No Type', or null/undefined. - const typeName = issue.type ? issue.type.name : null; - const isBugOrUntyped = typeName === 'Bug' || typeName === 'No Type' || !typeName; - - if (isOld && isStale && isUnpopular && isBugOrUntyped) { - const message = `Closing stale issue #${issue.number}: "${issue.title}" (Created: ${createdAt.toISOString()}, Reactions: ${reactionCount})`; - - processedCount++; - - if (dryRun) { - core.info(`[DRY RUN] ${message}`); - continue; - } - - core.info(message); - + // Using listForRepo directly instead of paginate to limit to first 100 results + const response = await github.rest.issues.listForRepo(opts); + const issues = response.data; + core.info(`Found ${issues.length} open issues to check (limit 100).`); + + // Initial Count of Issue Types in this batch + let bugCount = 0; + let noTypeCount = 0; + let otherTypeCount = 0; + + for (const issue of issues) { + const typeName = issue.type ? issue.type.name : null; + if (typeName === 'Bug') { + bugCount++; + } else if (!typeName || typeName === 'No Type') { + noTypeCount++; + } else { + otherTypeCount++; + } + } + core.info(`Issue Type Distribution in this batch: Bug=${bugCount}, No Type=${noTypeCount}, Other=${otherTypeCount}`); + + let processedCount = 0; + + for (const issue of issues) { + // Safety Check: Skip Pull Requests (GitHub API treats PRs as Issues in list endpoints) + if (issue.pull_request) continue; + + const createdAt = new Date(issue.created_at); + const updatedAt = new Date(issue.updated_at); + const reactionCount = issue.reactions.total_count; + + // 3. Evaluate Conditions + const isOld = createdAt < threeMonthsAgo; + const isStale = updatedAt < tenDaysAgo; + const isUnpopular = reactionCount < 5; + + // 4. Check Issue Type (Must be 'Bug' or 'No Type') + const typeName = issue.type ? issue.type.name : null; + const isBugOrUntyped = typeName === 'Bug' || typeName === 'No Type' || !typeName; + + if (isOld && isStale && isUnpopular && isBugOrUntyped) { + const message = `Closing stale issue #${issue.number}: "${issue.title}" (${issue.html_url}) (Created: ${createdAt.toISOString()}, Reactions: ${reactionCount})`; + + processedCount++; + + if (dryRun) { + core.info(`[DRY RUN] ${message}`); + continue; + } + + core.info(message); // 4. Comment // await github.rest.issues.createComment({ // owner: context.repo.owner, From a7a3000fa3dd173dac48ea817a116d0658477fb7 Mon Sep 17 00:00:00 2001 From: galz10 Date: Tue, 2 Dec 2025 15:10:53 -0800 Subject: [PATCH 12/23] feat: group stale issue logs by type --- .../gemini-scheduled-stale-issue-closer.yml | 148 ++++++++++-------- 1 file changed, 82 insertions(+), 66 deletions(-) diff --git a/.github/workflows/gemini-scheduled-stale-issue-closer.yml b/.github/workflows/gemini-scheduled-stale-issue-closer.yml index d2ec105bb61..8703f2a6468 100644 --- a/.github/workflows/gemini-scheduled-stale-issue-closer.yml +++ b/.github/workflows/gemini-scheduled-stale-issue-closer.yml @@ -70,78 +70,94 @@ jobs: per_page: 100 }); - // Using listForRepo directly instead of paginate to limit to first 100 results - const response = await github.rest.issues.listForRepo(opts); - const issues = response.data; - core.info(`Found ${issues.length} open issues to check (limit 100).`); - - // Initial Count of Issue Types in this batch - let bugCount = 0; - let noTypeCount = 0; - let otherTypeCount = 0; - - for (const issue of issues) { - const typeName = issue.type ? issue.type.name : null; - if (typeName === 'Bug') { - bugCount++; - } else if (!typeName || typeName === 'No Type') { - noTypeCount++; - } else { - otherTypeCount++; - } - } - core.info(`Issue Type Distribution in this batch: Bug=${bugCount}, No Type=${noTypeCount}, Other=${otherTypeCount}`); - - let processedCount = 0; - - for (const issue of issues) { - // Safety Check: Skip Pull Requests (GitHub API treats PRs as Issues in list endpoints) - if (issue.pull_request) continue; - - const createdAt = new Date(issue.created_at); - const updatedAt = new Date(issue.updated_at); - const reactionCount = issue.reactions.total_count; - - // 3. Evaluate Conditions - const isOld = createdAt < threeMonthsAgo; - const isStale = updatedAt < tenDaysAgo; - const isUnpopular = reactionCount < 5; - - // 4. Check Issue Type (Must be 'Bug' or 'No Type') - const typeName = issue.type ? issue.type.name : null; - const isBugOrUntyped = typeName === 'Bug' || typeName === 'No Type' || !typeName; - - if (isOld && isStale && isUnpopular && isBugOrUntyped) { - const message = `Closing stale issue #${issue.number}: "${issue.title}" (${issue.html_url}) (Created: ${createdAt.toISOString()}, Reactions: ${reactionCount})`; - - processedCount++; - - if (dryRun) { - core.info(`[DRY RUN] ${message}`); - continue; - } + // Using listForRepo directly instead of paginate to limit to first 100 results + const response = await github.rest.issues.listForRepo(opts); + const issues = response.data; + core.info(`Found ${issues.length} open issues to check (limit 100).`); + + // Initial Count of Issue Types in this batch + let bugCount = 0; + let noTypeCount = 0; + let otherTypeCount = 0; + + for (const issue of issues) { + const typeName = issue.type ? issue.type.name : null; + if (typeName === 'Bug') { + bugCount++; + } else if (!typeName || typeName === 'No Type') { + noTypeCount++; + } else { + otherTypeCount++; + } + } + core.info(`Issue Type Distribution in this batch: Bug=${bugCount}, No Type=${noTypeCount}, Other=${otherTypeCount}`); + + let processedCount = 0; - core.info(message); - // 4. Comment - // await github.rest.issues.createComment({ - // owner: context.repo.owner, - // repo: context.repo.repo, - // issue_number: issue.number, - // body: "Hello! As part of our effort to keep our backlog manageable and focus on the most active issues, we are tidying up older reports.\n\nIt looks like this issue hasn't been active for a while, so we are closing it for now. However, if you are still experiencing this bug on the latest stable build, please feel free to reopen this issue or create a new one with updated details.\n\nThank you for your contribution!" - // }); + // Buckets for logging + const bugLogs = []; + const noTypeLogs = []; + const otherLogs = []; // Should be empty given logic, but good for debugging + for (const issue of issues) { + // Safety Check: Skip Pull Requests (GitHub API treats PRs as Issues in list endpoints) + if (issue.pull_request) continue; + + const createdAt = new Date(issue.created_at); + const updatedAt = new Date(issue.updated_at); + const reactionCount = issue.reactions.total_count; + + // 3. Evaluate Conditions + const isOld = createdAt < threeMonthsAgo; + const isStale = updatedAt < tenDaysAgo; + const isUnpopular = reactionCount < 5; + + // 4. Check Issue Type (Must be 'Bug' or 'No Type') + const typeName = issue.type ? issue.type.name : null; + const isBug = typeName === 'Bug'; + const isNoType = !typeName || typeName === 'No Type'; + + if (isOld && isStale && isUnpopular && (isBug || isNoType)) { + const message = `Closing stale issue #${issue.number}: "${issue.title}" (${issue.html_url}) (Created: ${createdAt.toISOString()}, Reactions: ${reactionCount})`; + + processedCount++; + + if (isBug) { + bugLogs.push(message); + } else if (isNoType) { + noTypeLogs.push(message); + } else { + otherLogs.push(message); + } + + // Actions (Only executed if not dry run and logic uncommented) + // 4. Comment + // await github.rest.issues.createComment({ ... }); // 5. Close - // await github.rest.issues.update({ - // owner: context.repo.owner, - // repo: context.repo.repo, - // issue_number: issue.number, - // state: 'closed' - // }); + // await github.rest.issues.update({ ... }); } } + const prefix = dryRun ? '[DRY RUN] ' : ''; + + if (bugLogs.length > 0) { + core.info(`\n=== ${prefix}Bug Issues (${bugLogs.length}) ===`); + bugLogs.forEach(log => core.info(`${prefix}${log}`)); + } + + if (noTypeLogs.length > 0) { + core.info(`\n=== ${prefix}No Type / Untyped Issues (${noTypeLogs.length}) ===`); + noTypeLogs.forEach(log => core.info(`${prefix}${log}`)); + } + + if (otherLogs.length > 0) { + core.info(`\n=== ${prefix}Other Issues (Unexpected) (${otherLogs.length}) ===`); + otherLogs.forEach(log => core.info(`${prefix}${log}`)); + } + if (dryRun) { - core.info(`[DRY RUN] Total issues that would be closed: ${processedCount}`); + core.info(`\n[DRY RUN] Total issues that would be closed: ${processedCount}`); } else { - core.info(`Total issues closed: ${processedCount}`); + core.info(`\nTotal issues closed: ${processedCount}`); } + From 58eeeebd51c75b13ab6b027314b21a2dc4c45ba9 Mon Sep 17 00:00:00 2001 From: galz10 Date: Tue, 2 Dec 2025 15:13:27 -0800 Subject: [PATCH 13/23] fix lint errors --- .github/workflows/gemini-scheduled-stale-issue-closer.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/gemini-scheduled-stale-issue-closer.yml b/.github/workflows/gemini-scheduled-stale-issue-closer.yml index 8703f2a6468..a5230d73d40 100644 --- a/.github/workflows/gemini-scheduled-stale-issue-closer.yml +++ b/.github/workflows/gemini-scheduled-stale-issue-closer.yml @@ -93,7 +93,7 @@ jobs: core.info(`Issue Type Distribution in this batch: Bug=${bugCount}, No Type=${noTypeCount}, Other=${otherTypeCount}`); let processedCount = 0; - + // Buckets for logging const bugLogs = []; const noTypeLogs = []; @@ -119,9 +119,9 @@ jobs: if (isOld && isStale && isUnpopular && (isBug || isNoType)) { const message = `Closing stale issue #${issue.number}: "${issue.title}" (${issue.html_url}) (Created: ${createdAt.toISOString()}, Reactions: ${reactionCount})`; - + processedCount++; - + if (isBug) { bugLogs.push(message); } else if (isNoType) { @@ -160,4 +160,3 @@ jobs: } else { core.info(`\nTotal issues closed: ${processedCount}`); } - From 82df7c207d0a525ea1cee0167501c37bf755b627 Mon Sep 17 00:00:00 2001 From: galz10 Date: Tue, 2 Dec 2025 15:16:27 -0800 Subject: [PATCH 14/23] feat: process all open issues --- .../workflows/gemini-scheduled-stale-issue-closer.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/gemini-scheduled-stale-issue-closer.yml b/.github/workflows/gemini-scheduled-stale-issue-closer.yml index a5230d73d40..a859c3ac799 100644 --- a/.github/workflows/gemini-scheduled-stale-issue-closer.yml +++ b/.github/workflows/gemini-scheduled-stale-issue-closer.yml @@ -60,7 +60,7 @@ jobs: core.info(`Cutoff date for creation (3 months ago): ${threeMonthsAgo.toISOString()}`); core.info(`Cutoff date for updates (10 days ago): ${tenDaysAgo.toISOString()}`); - // 2. Fetch all open issues (Limited to 100 for debugging) + // 2. Fetch all open issues (No limit, fetching all pages) const opts = github.rest.issues.listForRepo.endpoint.merge({ owner: context.repo.owner, repo: context.repo.repo, @@ -70,10 +70,9 @@ jobs: per_page: 100 }); - // Using listForRepo directly instead of paginate to limit to first 100 results - const response = await github.rest.issues.listForRepo(opts); - const issues = response.data; - core.info(`Found ${issues.length} open issues to check (limit 100).`); + // Using paginate to fetch all open issues + const issues = await github.paginate(opts); + core.info(`Found ${issues.length} open issues to check.`); // Initial Count of Issue Types in this batch let bugCount = 0; From 1a35c9ba8169ef68d817dbf99d91e32a3829b19d Mon Sep 17 00:00:00 2001 From: galz10 Date: Tue, 2 Dec 2025 15:25:19 -0800 Subject: [PATCH 15/23] fix: exclude PRs from total count and loops --- .../workflows/gemini-scheduled-stale-issue-closer.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/gemini-scheduled-stale-issue-closer.yml b/.github/workflows/gemini-scheduled-stale-issue-closer.yml index a859c3ac799..9af55dd2a37 100644 --- a/.github/workflows/gemini-scheduled-stale-issue-closer.yml +++ b/.github/workflows/gemini-scheduled-stale-issue-closer.yml @@ -71,8 +71,12 @@ jobs: }); // Using paginate to fetch all open issues - const issues = await github.paginate(opts); - core.info(`Found ${issues.length} open issues to check.`); + const allItems = await github.paginate(opts); + + // Filter out Pull Requests (GitHub API returns mixed results) + const issues = allItems.filter(item => !item.pull_request); + + core.info(`Found ${issues.length} open issues to check (excluding PRs).`); // Initial Count of Issue Types in this batch let bugCount = 0; @@ -99,9 +103,6 @@ jobs: const otherLogs = []; // Should be empty given logic, but good for debugging for (const issue of issues) { - // Safety Check: Skip Pull Requests (GitHub API treats PRs as Issues in list endpoints) - if (issue.pull_request) continue; - const createdAt = new Date(issue.created_at); const updatedAt = new Date(issue.updated_at); const reactionCount = issue.reactions.total_count; From a4e456ca45bd70619531deff582ae8c481b1bb8c Mon Sep 17 00:00:00 2001 From: galz10 Date: Tue, 2 Dec 2025 15:39:14 -0800 Subject: [PATCH 16/23] feat: ignore bot updates for staleness check --- .../gemini-scheduled-stale-issue-closer.yml | 55 +++++++++++++++++-- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/.github/workflows/gemini-scheduled-stale-issue-closer.yml b/.github/workflows/gemini-scheduled-stale-issue-closer.yml index 9af55dd2a37..d80cc2cb47b 100644 --- a/.github/workflows/gemini-scheduled-stale-issue-closer.yml +++ b/.github/workflows/gemini-scheduled-stale-issue-closer.yml @@ -109,15 +109,62 @@ jobs: // 3. Evaluate Conditions const isOld = createdAt < threeMonthsAgo; - const isStale = updatedAt < tenDaysAgo; const isUnpopular = reactionCount < 5; + + // Determine staleness (considering Bot updates) + let isStale = updatedAt < tenDaysAgo; + + if (!isStale && isOld && isUnpopular) { + // If it looks active, check if the last update was just a bot + // We only do this API call if the issue is OLD and UNPOPULAR, to save rate limits + try { + const comments = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + per_page: 10, // Check last 10 comments + sort: 'created', + direction: 'desc' + }); + + let lastHumanDate = null; + + // Iterate comments to find last human + for (const comment of comments.data) { + if (comment.user.type !== 'Bot') { + lastHumanDate = new Date(comment.created_at); + break; // Found the latest human comment + } + } + + // If we found a human comment, check its date + if (lastHumanDate) { + isStale = lastHumanDate < tenDaysAgo; + } else { + // No human comments in the last 10 (or at all if few), + // check the issue creator. If creator is human, use createdAt. + // If creator is Bot, and no human comments, it's stale. + if (issue.user.type !== 'Bot') { + isStale = createdAt < tenDaysAgo; + } else { + isStale = true; // Bot created, no human comments + } + } + + if (isStale) { + core.info(`Issue #${issue.number} is seemingly active (updated ${updatedAt.toISOString()}) but last human interaction was stale.`); + } + + } catch (error) { + core.warning(`Failed to fetch comments for check #${issue.number}: ${error.message}`); + } + } // 4. Check Issue Type (Must be 'Bug' or 'No Type') const typeName = issue.type ? issue.type.name : null; - const isBug = typeName === 'Bug'; - const isNoType = !typeName || typeName === 'No Type'; + const isBugOrUntyped = typeName === 'Bug' || typeName === 'No Type' || !typeName; - if (isOld && isStale && isUnpopular && (isBug || isNoType)) { + if (isOld && isStale && isUnpopular && isBugOrUntyped) { const message = `Closing stale issue #${issue.number}: "${issue.title}" (${issue.html_url}) (Created: ${createdAt.toISOString()}, Reactions: ${reactionCount})`; processedCount++; From 32f83ca812889a261c713659a155ce91cf84a5ed Mon Sep 17 00:00:00 2001 From: galz10 Date: Tue, 2 Dec 2025 15:40:39 -0800 Subject: [PATCH 17/23] fix: variable scope issue for isBug/isNoType --- .github/workflows/gemini-scheduled-stale-issue-closer.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gemini-scheduled-stale-issue-closer.yml b/.github/workflows/gemini-scheduled-stale-issue-closer.yml index d80cc2cb47b..7ccf188140d 100644 --- a/.github/workflows/gemini-scheduled-stale-issue-closer.yml +++ b/.github/workflows/gemini-scheduled-stale-issue-closer.yml @@ -162,9 +162,10 @@ jobs: // 4. Check Issue Type (Must be 'Bug' or 'No Type') const typeName = issue.type ? issue.type.name : null; - const isBugOrUntyped = typeName === 'Bug' || typeName === 'No Type' || !typeName; + const isBug = typeName === 'Bug'; + const isNoType = !typeName || typeName === 'No Type'; - if (isOld && isStale && isUnpopular && isBugOrUntyped) { + if (isOld && isStale && isUnpopular && (isBug || isNoType)) { const message = `Closing stale issue #${issue.number}: "${issue.title}" (${issue.html_url}) (Created: ${createdAt.toISOString()}, Reactions: ${reactionCount})`; processedCount++; From 11b6f6a9f59549ed602c8d706c1b79ae77f08a83 Mon Sep 17 00:00:00 2001 From: galz10 Date: Tue, 2 Dec 2025 16:31:26 -0800 Subject: [PATCH 18/23] feat: finalize stale issue closer workflow logic --- .../gemini-scheduled-stale-issue-closer.yml | 155 +++++------------- 1 file changed, 42 insertions(+), 113 deletions(-) diff --git a/.github/workflows/gemini-scheduled-stale-issue-closer.yml b/.github/workflows/gemini-scheduled-stale-issue-closer.yml index 7ccf188140d..2660c4d47da 100644 --- a/.github/workflows/gemini-scheduled-stale-issue-closer.yml +++ b/.github/workflows/gemini-scheduled-stale-issue-closer.yml @@ -10,9 +10,6 @@ on: required: false default: true type: 'boolean' - push: - branches: - - 'galzahavi/add/stale-issue-workflow' concurrency: group: '${{ github.workflow }}' @@ -37,6 +34,7 @@ jobs: with: app-id: '${{ secrets.APP_ID }}' private-key: '${{ secrets.PRIVATE_KEY }}' + permission-issues: 'write' - name: 'Process Stale Issues' uses: 'actions/github-script@v7' @@ -50,17 +48,15 @@ jobs: core.info('DRY RUN MODE ENABLED: No changes will be applied.'); } - // 1. Setup Date Thresholds const threeMonthsAgo = new Date(); threeMonthsAgo.setMonth(threeMonthsAgo.getMonth() - 3); const tenDaysAgo = new Date(); tenDaysAgo.setDate(tenDaysAgo.getDate() - 10); - core.info(`Cutoff date for creation (3 months ago): ${threeMonthsAgo.toISOString()}`); - core.info(`Cutoff date for updates (10 days ago): ${tenDaysAgo.toISOString()}`); + core.info(`Cutoff date for creation: ${threeMonthsAgo.toISOString()}`); + core.info(`Cutoff date for updates: ${tenDaysAgo.toISOString()}`); - // 2. Fetch all open issues (No limit, fetching all pages) const opts = github.rest.issues.listForRepo.endpoint.merge({ owner: context.repo.owner, repo: context.repo.repo, @@ -70,141 +66,74 @@ jobs: per_page: 100 }); - // Using paginate to fetch all open issues - const allItems = await github.paginate(opts); - - // Filter out Pull Requests (GitHub API returns mixed results) - const issues = allItems.filter(item => !item.pull_request); - - core.info(`Found ${issues.length} open issues to check (excluding PRs).`); - - // Initial Count of Issue Types in this batch - let bugCount = 0; - let noTypeCount = 0; - let otherTypeCount = 0; - - for (const issue of issues) { - const typeName = issue.type ? issue.type.name : null; - if (typeName === 'Bug') { - bugCount++; - } else if (!typeName || typeName === 'No Type') { - noTypeCount++; - } else { - otherTypeCount++; - } - } - core.info(`Issue Type Distribution in this batch: Bug=${bugCount}, No Type=${noTypeCount}, Other=${otherTypeCount}`); + const issues = await github.paginate(opts); + const itemsToCheck = issues.filter(item => !item.pull_request); - let processedCount = 0; + core.info(`Found ${itemsToCheck.length} open issues to check.`); - // Buckets for logging - const bugLogs = []; - const noTypeLogs = []; - const otherLogs = []; // Should be empty given logic, but good for debugging + let processedCount = 0; - for (const issue of issues) { + for (const issue of itemsToCheck) { const createdAt = new Date(issue.created_at); const updatedAt = new Date(issue.updated_at); const reactionCount = issue.reactions.total_count; - // 3. Evaluate Conditions - const isOld = createdAt < threeMonthsAgo; - const isUnpopular = reactionCount < 5; - - // Determine staleness (considering Bot updates) + // Basic thresholds + if (createdAt >= threeMonthsAgo || reactionCount >= 5) { + continue; + } + let isStale = updatedAt < tenDaysAgo; - if (!isStale && isOld && isUnpopular) { - // If it looks active, check if the last update was just a bot - // We only do this API call if the issue is OLD and UNPOPULAR, to save rate limits + // If apparently active, check if it's only bot activity + if (!isStale) { try { const comments = await github.rest.issues.listComments({ owner: context.repo.owner, repo: context.repo.repo, issue_number: issue.number, - per_page: 10, // Check last 10 comments + per_page: 100, sort: 'created', direction: 'desc' }); - - let lastHumanDate = null; - - // Iterate comments to find last human - for (const comment of comments.data) { - if (comment.user.type !== 'Bot') { - lastHumanDate = new Date(comment.created_at); - break; // Found the latest human comment - } - } - - // If we found a human comment, check its date - if (lastHumanDate) { - isStale = lastHumanDate < tenDaysAgo; + + const lastHumanComment = comments.data.find(comment => comment.user.type !== 'Bot'); + if (lastHumanComment) { + isStale = new Date(lastHumanComment.created_at) < tenDaysAgo; } else { - // No human comments in the last 10 (or at all if few), - // check the issue creator. If creator is human, use createdAt. - // If creator is Bot, and no human comments, it's stale. + // No human comments. Check if creator is human. if (issue.user.type !== 'Bot') { isStale = createdAt < tenDaysAgo; } else { - isStale = true; // Bot created, no human comments + isStale = true; // Bot created, only bot comments } } - - if (isStale) { - core.info(`Issue #${issue.number} is seemingly active (updated ${updatedAt.toISOString()}) but last human interaction was stale.`); - } - } catch (error) { - core.warning(`Failed to fetch comments for check #${issue.number}: ${error.message}`); + core.warning(`Failed to fetch comments for issue #${issue.number}: ${error.message}`); + continue; } } - // 4. Check Issue Type (Must be 'Bug' or 'No Type') - const typeName = issue.type ? issue.type.name : null; - const isBug = typeName === 'Bug'; - const isNoType = !typeName || typeName === 'No Type'; - - if (isOld && isStale && isUnpopular && (isBug || isNoType)) { - const message = `Closing stale issue #${issue.number}: "${issue.title}" (${issue.html_url}) (Created: ${createdAt.toISOString()}, Reactions: ${reactionCount})`; - + if (isStale) { processedCount++; - - if (isBug) { - bugLogs.push(message); - } else if (isNoType) { - noTypeLogs.push(message); - } else { - otherLogs.push(message); + const message = `Closing stale issue #${issue.number}: "${issue.title}" (${issue.html_url})`; + core.info(message); + + if (!dryRun) { + // await github.rest.issues.createComment({ + // owner: context.repo.owner, + // repo: context.repo.repo, + // issue_number: issue.number, + // body: 'Hello! As part of our effort to keep our backlog manageable and focus on the most active issues, we are tidying up older reports.\n\nIt looks like this issue hasn't been active for a while, so we are closing it for now. However, if you are still experiencing this bug on the latest stable build, please feel free to comment on this issue or create a new one with updated details.\n\nThank you for your contribution!' + // }); + // await github.rest.issues.update({ + // owner: context.repo.owner, + // repo: context.repo.repo, + // issue_number: issue.number, + // state: 'closed' + // }); } - - // Actions (Only executed if not dry run and logic uncommented) - // 4. Comment - // await github.rest.issues.createComment({ ... }); - // 5. Close - // await github.rest.issues.update({ ... }); } } - const prefix = dryRun ? '[DRY RUN] ' : ''; - - if (bugLogs.length > 0) { - core.info(`\n=== ${prefix}Bug Issues (${bugLogs.length}) ===`); - bugLogs.forEach(log => core.info(`${prefix}${log}`)); - } - - if (noTypeLogs.length > 0) { - core.info(`\n=== ${prefix}No Type / Untyped Issues (${noTypeLogs.length}) ===`); - noTypeLogs.forEach(log => core.info(`${prefix}${log}`)); - } - - if (otherLogs.length > 0) { - core.info(`\n=== ${prefix}Other Issues (Unexpected) (${otherLogs.length}) ===`); - otherLogs.forEach(log => core.info(`${prefix}${log}`)); - } - - if (dryRun) { - core.info(`\n[DRY RUN] Total issues that would be closed: ${processedCount}`); - } else { - core.info(`\nTotal issues closed: ${processedCount}`); - } + core.info(`\nTotal issues processed: ${processedCount}`); From 29f846d58c5c678217d07656f4811bdbd097b6c6 Mon Sep 17 00:00:00 2001 From: galz10 Date: Tue, 2 Dec 2025 17:41:21 -0800 Subject: [PATCH 19/23] feat: enable auto-closing with label --- .../gemini-scheduled-stale-issue-closer.yml | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/.github/workflows/gemini-scheduled-stale-issue-closer.yml b/.github/workflows/gemini-scheduled-stale-issue-closer.yml index 2660c4d47da..92493489be6 100644 --- a/.github/workflows/gemini-scheduled-stale-issue-closer.yml +++ b/.github/workflows/gemini-scheduled-stale-issue-closer.yml @@ -47,6 +47,7 @@ jobs: if (dryRun) { core.info('DRY RUN MODE ENABLED: No changes will be applied.'); } + const batchLabel = 'Stale'; const threeMonthsAgo = new Date(); threeMonthsAgo.setMonth(threeMonthsAgo.getMonth() - 3); @@ -120,18 +121,29 @@ jobs: core.info(message); if (!dryRun) { - // await github.rest.issues.createComment({ - // owner: context.repo.owner, - // repo: context.repo.repo, - // issue_number: issue.number, - // body: 'Hello! As part of our effort to keep our backlog manageable and focus on the most active issues, we are tidying up older reports.\n\nIt looks like this issue hasn't been active for a while, so we are closing it for now. However, if you are still experiencing this bug on the latest stable build, please feel free to comment on this issue or create a new one with updated details.\n\nThank you for your contribution!' - // }); - // await github.rest.issues.update({ - // owner: context.repo.owner, - // repo: context.repo.repo, - // issue_number: issue.number, - // state: 'closed' - // }); + // Add label + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + labels: [batchLabel] + }); + + // Add comment + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + body: 'Hello! As part of our effort to keep our backlog manageable and focus on the most active issues, we are tidying up older reports.\n\nIt looks like this issue hasn't been active for a while, so we are closing it for now. However, if you are still experiencing this bug on the latest stable build, please feel free to comment on this issue or create a new one with updated details.\n\nThank you for your contribution!' + }); + + // Close issue + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + state: 'closed' + }); } } } From 1f4cb3a345ff365a7b05d6eb87c803da6dd24b61 Mon Sep 17 00:00:00 2001 From: galz10 Date: Tue, 2 Dec 2025 17:42:48 -0800 Subject: [PATCH 20/23] fix: escape single quote in comment body --- .github/workflows/gemini-scheduled-stale-issue-closer.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gemini-scheduled-stale-issue-closer.yml b/.github/workflows/gemini-scheduled-stale-issue-closer.yml index 92493489be6..4a4e420f0ae 100644 --- a/.github/workflows/gemini-scheduled-stale-issue-closer.yml +++ b/.github/workflows/gemini-scheduled-stale-issue-closer.yml @@ -134,7 +134,7 @@ jobs: owner: context.repo.owner, repo: context.repo.repo, issue_number: issue.number, - body: 'Hello! As part of our effort to keep our backlog manageable and focus on the most active issues, we are tidying up older reports.\n\nIt looks like this issue hasn't been active for a while, so we are closing it for now. However, if you are still experiencing this bug on the latest stable build, please feel free to comment on this issue or create a new one with updated details.\n\nThank you for your contribution!' + body: 'Hello! As part of our effort to keep our backlog manageable and focus on the most active issues, we are tidying up older reports.\n\nIt looks like this issue hasn\'t been active for a while, so we are closing it for now. However, if you are still experiencing this bug on the latest stable build, please feel free to comment on this issue or create a new one with updated details.\n\nThank you for your contribution!' }); // Close issue From 4329e620188ec8f31ce0eb88d4dd5b99cc54c2f0 Mon Sep 17 00:00:00 2001 From: galz10 Date: Tue, 2 Dec 2025 17:45:04 -0800 Subject: [PATCH 21/23] Change default value of dry run input to false --- .github/workflows/gemini-scheduled-stale-issue-closer.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gemini-scheduled-stale-issue-closer.yml b/.github/workflows/gemini-scheduled-stale-issue-closer.yml index 4a4e420f0ae..d8310e78f34 100644 --- a/.github/workflows/gemini-scheduled-stale-issue-closer.yml +++ b/.github/workflows/gemini-scheduled-stale-issue-closer.yml @@ -8,7 +8,7 @@ on: dry_run: description: 'Run in dry-run mode (no changes applied)' required: false - default: true + default: false type: 'boolean' concurrency: From af6bed26d2085ff8ddef150fc2b68be114387fc7 Mon Sep 17 00:00:00 2001 From: galz10 Date: Wed, 3 Dec 2025 10:46:05 -0800 Subject: [PATCH 22/23] Fix lint issue --- .github/workflows/gemini-scheduled-stale-issue-closer.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gemini-scheduled-stale-issue-closer.yml b/.github/workflows/gemini-scheduled-stale-issue-closer.yml index d8310e78f34..7daa12db1ca 100644 --- a/.github/workflows/gemini-scheduled-stale-issue-closer.yml +++ b/.github/workflows/gemini-scheduled-stale-issue-closer.yml @@ -136,7 +136,7 @@ jobs: issue_number: issue.number, body: 'Hello! As part of our effort to keep our backlog manageable and focus on the most active issues, we are tidying up older reports.\n\nIt looks like this issue hasn\'t been active for a while, so we are closing it for now. However, if you are still experiencing this bug on the latest stable build, please feel free to comment on this issue or create a new one with updated details.\n\nThank you for your contribution!' }); - + // Close issue await github.rest.issues.update({ owner: context.repo.owner, From a8cbac5697be654f03914c81cf6c9d633071f957 Mon Sep 17 00:00:00 2001 From: galz10 Date: Wed, 3 Dec 2025 10:57:43 -0800 Subject: [PATCH 23/23] Refactor: Optimize stale issue closer workflow --- .../gemini-scheduled-stale-issue-closer.yml | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/.github/workflows/gemini-scheduled-stale-issue-closer.yml b/.github/workflows/gemini-scheduled-stale-issue-closer.yml index 7daa12db1ca..47b1f07e181 100644 --- a/.github/workflows/gemini-scheduled-stale-issue-closer.yml +++ b/.github/workflows/gemini-scheduled-stale-issue-closer.yml @@ -25,9 +25,6 @@ jobs: permissions: issues: 'write' steps: - - name: 'Checkout' - uses: 'actions/checkout@v4' - - name: 'Generate GitHub App Token' id: 'generate_token' uses: 'actions/create-github-app-token@v1' @@ -58,18 +55,16 @@ jobs: core.info(`Cutoff date for creation: ${threeMonthsAgo.toISOString()}`); core.info(`Cutoff date for updates: ${tenDaysAgo.toISOString()}`); - const opts = github.rest.issues.listForRepo.endpoint.merge({ - owner: context.repo.owner, - repo: context.repo.repo, - state: 'open', + const query = `repo:${context.repo.owner}/${context.repo.repo} is:issue is:open created:<${threeMonthsAgo.toISOString()}`; + core.info(`Searching with query: ${query}`); + + const itemsToCheck = await github.paginate(github.rest.search.issuesAndPullRequests, { + q: query, sort: 'created', - direction: 'asc', + order: 'asc', per_page: 100 }); - const issues = await github.paginate(opts); - const itemsToCheck = issues.filter(item => !item.pull_request); - core.info(`Found ${itemsToCheck.length} open issues to check.`); let processedCount = 0; @@ -80,7 +75,7 @@ jobs: const reactionCount = issue.reactions.total_count; // Basic thresholds - if (createdAt >= threeMonthsAgo || reactionCount >= 5) { + if (reactionCount >= 5) { continue; }