surshar / claude-code
Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.
Built by Anthropic · github.com/anthropics
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) <noreply@anthropic.com>
| @@ -45,7 +45,7 @@ | ||
| 45 | 45 | ALLOWED LABELS — you may ONLY use labels from this list. Never invent new labels. |
| 46 | 46 | |
| 47 | 47 | Type: bug, enhancement, question, documentation, duplicate, invalid |
| 48 | - Lifecycle: needs-repro, needs-info | |
| 48 | + Lifecycle: needs-repro, needs-info, stale, autoclose | |
| 49 | 49 | Platform: platform:linux, platform:macos, platform:windows, platform:wsl, platform:ios, platform:android, platform:vscode, platform:intellij, platform:web, platform:aws-bedrock |
| 50 | 50 | API: api:bedrock, api:vertex |
| 51 | 51 | |
| @@ -85,10 +85,12 @@ | ||
| 85 | 85 | **If EVENT is "issue_comment" (comment on existing issue):** |
| 86 | 86 | |
| 87 | 87 | 3. Evaluate lifecycle labels based on the full conversation: |
| 88 | + - If the issue has `stale` or `autoclose`, remove the label — a new human comment means the issue is still active: | |
| 89 | + `gh issue edit ${{ github.event.issue.number }} --remove-label "stale" --remove-label "autoclose"` | |
| 88 | 90 | - If the issue has `needs-repro` or `needs-info` and the missing information has now been provided, remove the label: |
| 89 | 91 | `gh issue edit ${{ github.event.issue.number }} --remove-label "needs-repro"` |
| 90 | 92 | - 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. |
| 91 | - - Comments like "+1", "me too", "same here", or emoji reactions are NOT the missing information. Only remove labels when substantive details are actually provided. | |
| 93 | + - 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. | |
| 92 | 94 | - Do NOT add or remove category labels (bug, enhancement, etc.) on comment events. |
| 93 | 95 | |
| 94 | 96 | GUIDELINES: |
| @@ -70,11 +70,8 @@ | ||
| 70 | 70 | ); |
| 71 | 71 | if (alreadyStale) continue; |
| 72 | 72 | |
| 73 | - const isEnhancement = issue.labels?.some( | |
| 74 | - (l: any) => l.name === "enhancement" | |
| 75 | - ); | |
| 76 | 73 | const thumbsUp = issue.reactions?.["+1"] ?? 0; |
| 77 | - if (isEnhancement && thumbsUp >= STALE_UPVOTE_THRESHOLD) continue; | |
| 74 | + if (thumbsUp >= STALE_UPVOTE_THRESHOLD) continue; | |
| 78 | 75 | |
| 79 | 76 | const base = `/repos/${owner}/${repo}/issues/${issue.number}`; |
| 80 | 77 | |
| @@ -109,6 +106,10 @@ | ||
| 109 | 106 | for (const issue of issues) { |
| 110 | 107 | if (issue.pull_request) continue; |
| 111 | 108 | if (issue.locked) continue; |
| 109 | + | |
| 110 | + const thumbsUp = issue.reactions?.["+1"] ?? 0; | |
| 111 | + if (thumbsUp >= STALE_UPVOTE_THRESHOLD) continue; | |
| 112 | + | |
| 112 | 113 | const base = `/repos/${owner}/${repo}/issues/${issue.number}`; |
| 113 | 114 | |
| 114 | 115 | const events = await githubRequest<any[]>(`${base}/events?per_page=100`); |
| @@ -120,6 +121,22 @@ | ||
| 120 | 121 | |
| 121 | 122 | if (!labeledAt || labeledAt > cutoff) continue; |
| 122 | 123 | |
| 124 | + // Skip if a non-bot user commented after the label was applied. | |
| 125 | + // The triage workflow should remove lifecycle labels on human | |
| 126 | + // activity, but check here too as a safety net. | |
| 127 | + const comments = await githubRequest<any[]>( | |
| 128 | + `${base}/comments?since=${labeledAt.toISOString()}&per_page=100` | |
| 129 | + ); | |
| 130 | + const hasHumanComment = comments.some( | |
| 131 | + (c) => c.user && c.user.type !== "Bot" | |
| 132 | + ); | |
| 133 | + if (hasHumanComment) { | |
| 134 | + console.log( | |
| 135 | + `#${issue.number}: skipping (human activity after ${label} label)` | |
| 136 | + ); | |
| 137 | + continue; | |
| 138 | + } | |
| 139 | + | |
| 123 | 140 | if (DRY_RUN) { |
| 124 | 141 | const age = Math.floor((Date.now() - labeledAt.getTime()) / 86400000); |
| 125 | 142 | console.log(`#${issue.number}: would close (${label}, ${age}d old) — ${issue.title}`); |