From f0970ee09da622602b4bb40b6987b963bc09b631 Mon Sep 17 00:00:00 2001 From: Chris Lloyd Date: Tue, 17 Feb 2026 17:43:09 +0000 Subject: [PATCH] Fix issues being auto-closed despite human activity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sweep script was closing issues based solely on when a lifecycle label was applied, ignoring any human comments posted after the label. This caused active issues (like #11792) to be closed even when users responded to the stale warning. Three changes: 1. Teach the triage bot about `stale` and `autoclose` labels so it removes them when a human comments on the issue. 2. Add a safety net in `closeExpired()` that checks for non-bot comments posted after the lifecycle label was applied — if any exist, skip closing. 3. Extend the 10-upvote protection (which previously only applied to enhancements) to all issue types, in both `markStale()` and `closeExpired()`. Fixes #16497 ## Test plan Trace through scenarios manually: - Issue with stale label + human comment after → triage removes label; sweep skips even if triage hasn't run yet (safety net) - Issue with stale label + no human comment → closes as before - Issue with 10+ upvotes of any type → never marked stale or closed Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/claude-issue-triage.yml | 6 ++++-- scripts/sweep.ts | 25 +++++++++++++++++++---- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/.github/workflows/claude-issue-triage.yml b/.github/workflows/claude-issue-triage.yml index 95ed66f94..e86144d91 100644 --- a/.github/workflows/claude-issue-triage.yml +++ b/.github/workflows/claude-issue-triage.yml @@ -45,7 +45,7 @@ jobs: ALLOWED LABELS — you may ONLY use labels from this list. Never invent new labels. Type: bug, enhancement, question, documentation, duplicate, invalid - Lifecycle: needs-repro, needs-info + Lifecycle: needs-repro, needs-info, stale, autoclose Platform: platform:linux, platform:macos, platform:windows, platform:wsl, platform:ios, platform:android, platform:vscode, platform:intellij, platform:web, platform:aws-bedrock API: api:bedrock, api:vertex @@ -85,10 +85,12 @@ jobs: **If EVENT is "issue_comment" (comment on existing issue):** 3. Evaluate lifecycle labels based on the full conversation: + - If the issue has `stale` or `autoclose`, remove the label — a new human comment means the issue is still active: + `gh issue edit ${{ github.event.issue.number }} --remove-label "stale" --remove-label "autoclose"` - If the issue has `needs-repro` or `needs-info` and the missing information has now been provided, remove the label: `gh issue edit ${{ github.event.issue.number }} --remove-label "needs-repro"` - If the issue doesn't have lifecycle labels but clearly needs them (e.g., a maintainer asked for repro steps or more details), add the appropriate label. - - Comments like "+1", "me too", "same here", or emoji reactions are NOT the missing information. Only remove labels when substantive details are actually provided. + - Comments like "+1", "me too", "same here", or emoji reactions are NOT the missing information. Only remove `needs-repro` or `needs-info` when substantive details are actually provided. - Do NOT add or remove category labels (bug, enhancement, etc.) on comment events. GUIDELINES: diff --git a/scripts/sweep.ts b/scripts/sweep.ts index 41d09ac38..4709290be 100644 --- a/scripts/sweep.ts +++ b/scripts/sweep.ts @@ -70,11 +70,8 @@ async function markStale(owner: string, repo: string) { ); if (alreadyStale) continue; - const isEnhancement = issue.labels?.some( - (l: any) => l.name === "enhancement" - ); const thumbsUp = issue.reactions?.["+1"] ?? 0; - if (isEnhancement && thumbsUp >= STALE_UPVOTE_THRESHOLD) continue; + if (thumbsUp >= STALE_UPVOTE_THRESHOLD) continue; const base = `/repos/${owner}/${repo}/issues/${issue.number}`; @@ -109,6 +106,10 @@ async function closeExpired(owner: string, repo: string) { for (const issue of issues) { if (issue.pull_request) continue; if (issue.locked) continue; + + const thumbsUp = issue.reactions?.["+1"] ?? 0; + if (thumbsUp >= STALE_UPVOTE_THRESHOLD) continue; + const base = `/repos/${owner}/${repo}/issues/${issue.number}`; const events = await githubRequest(`${base}/events?per_page=100`); @@ -120,6 +121,22 @@ async function closeExpired(owner: string, repo: string) { if (!labeledAt || labeledAt > cutoff) continue; + // Skip if a non-bot user commented after the label was applied. + // The triage workflow should remove lifecycle labels on human + // activity, but check here too as a safety net. + const comments = await githubRequest( + `${base}/comments?since=${labeledAt.toISOString()}&per_page=100` + ); + const hasHumanComment = comments.some( + (c) => c.user && c.user.type !== "Bot" + ); + if (hasHumanComment) { + console.log( + `#${issue.number}: skipping (human activity after ${label} label)` + ); + continue; + } + if (DRY_RUN) { const age = Math.floor((Date.now() - labeledAt.getTime()) / 86400000); console.log(`#${issue.number}: would close (${label}, ${age}d old) — ${issue.title}`);