From 1398e0c963af2506bcbd11bd101ad7c6bba1f0c5 Mon Sep 17 00:00:00 2001 From: Bryan Morgan Date: Sun, 1 Feb 2026 14:48:29 -0500 Subject: [PATCH] fix: prevent stale PR closer from incorrectly closing new PRs This fixes a bug where PRs from maintainers (or any PR) could be immediately closed as stale even when just opened. The root causes: 1. If team member fetch failed silently, maintainerLogins was empty 2. lastActivity was initialized to epoch (1970), so any PR without detected maintainer activity appeared 50+ years old 3. No minimum age check - PRs created today could be marked stale Changes: - Add explicit check to skip PRs created < 30 days ago from staleness - Initialize lastActivity to PR creation date instead of epoch - Improve logging for team fetch success/failure with details - Add error logging for reviews/comments fetch failures Fixes issue where PR #18035 from a maintainer was incorrectly closed. --- .../gemini-scheduled-stale-pr-closer.yml | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/.github/workflows/gemini-scheduled-stale-pr-closer.yml b/.github/workflows/gemini-scheduled-stale-pr-closer.yml index 01696d77281..90d7417b055 100644 --- a/.github/workflows/gemini-scheduled-stale-pr-closer.yml +++ b/.github/workflows/gemini-scheduled-stale-pr-closer.yml @@ -43,14 +43,17 @@ jobs: // 1. Fetch maintainers for verification let maintainerLogins = new Set(); + let teamFetchSucceeded = false; try { const members = await github.paginate(github.rest.teams.listMembersInOrg, { org: context.repo.owner, team_slug: 'gemini-cli-maintainers' }); maintainerLogins = new Set(members.map(m => m.login.toLowerCase())); + teamFetchSucceeded = true; + core.info(`Successfully fetched ${maintainerLogins.size} team members from gemini-cli-maintainers`); } catch (e) { - core.warning('Failed to fetch team members'); + core.warning(`Failed to fetch team members from gemini-cli-maintainers: ${e.message}. Falling back to author_association only.`); } const isMaintainer = (login, assoc) => { @@ -154,7 +157,17 @@ jobs: const labels = pr.labels.map(l => l.name.toLowerCase()); if (labels.includes('help wanted') || labels.includes('🔒 maintainer only')) continue; - let lastActivity = new Date(0); + // Skip PRs that were created less than 30 days ago - they cannot be stale yet + const prCreatedAt = new Date(pr.created_at); + if (prCreatedAt > thirtyDaysAgo) { + const daysOld = Math.floor((Date.now() - prCreatedAt.getTime()) / (1000 * 60 * 60 * 24)); + core.info(`PR #${pr.number} was created ${daysOld} days ago. Skipping staleness check.`); + continue; + } + + // Initialize lastActivity to PR creation date (not epoch) as a safety baseline. + // This ensures we never incorrectly mark a PR as stale due to failed activity lookups. + let lastActivity = new Date(pr.created_at); try { const reviews = await github.paginate(github.rest.pulls.listReviews, { owner: context.repo.owner, @@ -178,8 +191,12 @@ jobs: if (d > lastActivity) lastActivity = d; } } - } catch (e) {} + } catch (e) { + core.warning(`Failed to fetch reviews/comments for PR #${pr.number}: ${e.message}`); + } + // For maintainer PRs, the PR creation itself counts as maintainer activity. + // (Now redundant since we initialize to pr.created_at, but kept for clarity) if (maintainerPr) { const d = new Date(pr.created_at); if (d > lastActivity) lastActivity = d;