Skip to content
Merged
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
f3026db
feat: add scheduled workflow to close stale issues
galz10 Dec 2, 2025
e1f47cf
fix: lint errors in workflow
galz10 Dec 2, 2025
06d77cc
Add temporary push trigger for testing
galz10 Dec 2, 2025
f420cec
fix: safe dry-run test mode
galz10 Dec 2, 2025
6ddfa95
chore: enable dry-run by default for testing
galz10 Dec 2, 2025
8f2fd47
feat: filter by issue type and add summary count
galz10 Dec 2, 2025
0b21009
fix: variable name reference
galz10 Dec 2, 2025
1e28256
fix: initialize processedCount variable
galz10 Dec 2, 2025
15aa41d
fix: log total processed count
galz10 Dec 2, 2025
0467a40
fix: lint errors
galz10 Dec 2, 2025
af4ddb5
feat: add type breakdown and issue links to logs
galz10 Dec 2, 2025
a7a3000
feat: group stale issue logs by type
galz10 Dec 2, 2025
58eeeeb
fix lint errors
galz10 Dec 2, 2025
82df7c2
feat: process all open issues
galz10 Dec 2, 2025
1a35c9b
fix: exclude PRs from total count and loops
galz10 Dec 2, 2025
a4e456c
feat: ignore bot updates for staleness check
galz10 Dec 2, 2025
32f83ca
fix: variable scope issue for isBug/isNoType
galz10 Dec 2, 2025
11b6f6a
feat: finalize stale issue closer workflow logic
galz10 Dec 3, 2025
29f846d
feat: enable auto-closing with label
galz10 Dec 3, 2025
1f4cb3a
fix: escape single quote in comment body
galz10 Dec 3, 2025
4329e62
Change default value of dry run input to false
galz10 Dec 3, 2025
62aa2e7
Merge branch 'main' into galzahavi/add/stale-issue-workflow
galz10 Dec 3, 2025
af6bed2
Fix lint issue
galz10 Dec 3, 2025
ef12970
Merge branch 'main' into galzahavi/add/stale-issue-workflow
galz10 Dec 3, 2025
a8cbac5
Refactor: Optimize stale issue closer workflow
galz10 Dec 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 146 additions & 0 deletions .github/workflows/gemini-scheduled-stale-issue-closer.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
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: '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 }}'
permission-issues: 'write'

- 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.');
}
const batchLabel = 'Stale';

const threeMonthsAgo = new Date();
threeMonthsAgo.setMonth(threeMonthsAgo.getMonth() - 3);

const tenDaysAgo = new Date();
tenDaysAgo.setDate(tenDaysAgo.getDate() - 10);

core.info(`Cutoff date for creation: ${threeMonthsAgo.toISOString()}`);
core.info(`Cutoff date for updates: ${tenDaysAgo.toISOString()}`);

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',
order: 'asc',
per_page: 100
});

core.info(`Found ${itemsToCheck.length} open issues to check.`);

let processedCount = 0;

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;

// Basic thresholds
if (reactionCount >= 5) {
continue;
}

let isStale = updatedAt < tenDaysAgo;

// 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: 100,
sort: 'created',
direction: 'desc'
});

const lastHumanComment = comments.data.find(comment => comment.user.type !== 'Bot');
if (lastHumanComment) {
isStale = new Date(lastHumanComment.created_at) < tenDaysAgo;
} else {
// No human comments. Check if creator is human.
if (issue.user.type !== 'Bot') {
isStale = createdAt < tenDaysAgo;
} else {
isStale = true; // Bot created, only bot comments
}
}
} catch (error) {
core.warning(`Failed to fetch comments for issue #${issue.number}: ${error.message}`);
continue;
}
}

if (isStale) {
processedCount++;
const message = `Closing stale issue #${issue.number}: "${issue.title}" (${issue.html_url})`;
core.info(message);

if (!dryRun) {
// 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'
});
}
}
}

core.info(`\nTotal issues processed: ${processedCount}`);