This document describes the automated lifecycle management system for stale and duplicate issues. The system applies progressive labels (stale, autoclose) to inactive issues and automatically closes them after grace periods, with multiple safeguards to detect human activity and prevent premature closure.
For information about initial duplicate detection and comment posting, see Issue Deduplication System. For information about label categorization on new issues, see Issue Triage System.
The stale issue management system consists of three primary workflows:
| Workflow | Purpose | Trigger |
|---|---|---|
sweep.yml | Labels stale issues and closes expired lifecycle labels | Scheduled (10:00, 22:00 UTC) + manual |
auto-close-duplicates.yml | Closes duplicate issues after 3-day grace period | Scheduled (09:00 UTC) + manual |
remove-autoclose-label.yml | Removes autoclose label when humans comment | On issue_comment.created event |
Sources: .github/workflows/sweep.yml .github/workflows/auto-close-duplicates.yml .github/workflows/remove-autoclose-label.yml
Issues progress through lifecycle stages defined in issue-lifecycle.ts:
Each lifecycle stage has a configurable duration and closure reason:
Exemption threshold: Issues with +1 reactions >= STALE_UPVOTE_THRESHOLD are never labeled as stale or autoclosed, regardless of inactivity.
Sources: scripts/sweep.ts3 scripts/sweep.ts74 scripts/sweep.ts111
markStale()The markStale() function identifies inactive open issues and applies the stale label:
Key implementation details:
updated_at ascending--dry-run flag (scripts/sweep.ts8)Sources: scripts/sweep.ts45-90
closeExpired()The closeExpired() function closes issues that have exceeded their lifecycle label timeout:
Critical safeguards:
labeled event timestamp, not updated_at, to determine label age (scripts/sweep.ts117-120)Closure behavior:
CLOSE_MESSAGE(reason) pointing users to create new issues (scripts/sweep.ts10-11)state: "closed" and state_reason: "not_planned" (scripts/sweep.ts145)Sources: scripts/sweep.ts92-154
The auto-close-duplicates.ts script automatically closes duplicate issues after a 3-day grace period:
Key implementation details:
Duplicate comment detection:
Sources: scripts/auto-close-duplicates.ts164-169
Issue number extraction supports two formats:
#123 short form (scripts/auto-close-duplicates.ts51-54)https://github.com/owner/repo/issues/123 URL form (scripts/auto-close-duplicates.ts56-60)Human disagreement check:
Sources: scripts/auto-close-duplicates.ts228-231
Closure format:
closedduplicate (not not_planned)duplicate labelSources: scripts/auto-close-duplicates.ts66-96
Sources: scripts/auto-close-duplicates.ts89-94
The remove-autoclose-label.yml workflow provides real-time response to human activity:
Workflow conditions (.github/workflows/remove-autoclose-label.yml13-16):
Error handling: Treats 404 responses as success (label already removed) (.github/workflows/remove-autoclose-label.yml37-41)
Sources: .github/workflows/remove-autoclose-label.yml
The closeExpired() function includes an additional safety check even though the triage workflow should have already removed lifecycle labels:
"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."
This redundant check (scripts/sweep.ts124-138) ensures issues are never auto-closed if humans engaged with them, even if the label removal workflow failed.
Sources: scripts/sweep.ts124-138
Concurrency control:
Ensures only one sweep runs at a time, preventing race conditions.
Sources: .github/workflows/sweep.yml3-12
No concurrency control, as duplicate closure is idempotent.
Sources: .github/workflows/auto-close-duplicates.yml3-6
Both scripts support dry-run mode for testing:
Sweep script:
Logs what would be done without making changes (scripts/sweep.ts8)
Auto-close duplicates: Uses DRY_RUN environment variable (defaults to true for safety):
Sources: scripts/backfill-duplicate-comments.ts91
The backfill-duplicate-comments.yml workflow retroactively processes old issues:
Default parameters:
days_back: 90 daysdry_run: trueMAX_ISSUE_NUMBER: 4050 (environment variable)Purpose: Ensures old issues get duplicate detection comments, enabling future auto-closure by the auto-close-duplicates.yml workflow.
Sources: .github/workflows/backfill-duplicate-comments.yml scripts/backfill-duplicate-comments.ts72-208
The auto-close-duplicates.yml workflow includes Statsig API key in its environment (.github/workflows/auto-close-duplicates.yml31), though the TypeScript script does not currently log events. This suggests future analytics integration.
The log-issue-events.yml workflow separately logs issue closures to Statsig with event name github_issue_created (appears to log all state changes despite the name).
Sources: .github/workflows/auto-close-duplicates.yml31 .github/workflows/log-issue-events.yml
| Parameter | Value | Location |
|---|---|---|
STALE_UPVOTE_THRESHOLD | Defined in issue-lifecycle.ts | scripts/sweep.ts3 |
| Duplicate grace period | 3 days | scripts/auto-close-duplicates.ts112-113 |
| Stale/autoclose timeouts | Defined in lifecycle array | scripts/sweep.ts3 |
| Sweep schedule | 10:00, 22:00 UTC | .github/workflows/sweep.yml5 |
| Duplicate auto-close schedule | 09:00 UTC | .github/workflows/auto-close-duplicates.yml5 |
| Max pagination (sweep) | 10 pages (1000 issues) | scripts/sweep.ts54 |
| Max pagination (auto-close) | 20 pages (2000 issues) | scripts/auto-close-duplicates.ts140 |
Both scripts handle GitHub API errors:
| Workflow | Timeout |
|---|---|
sweep.yml | None (default 360 minutes) |
auto-close-duplicates.yml | 10 minutes |
backfill-duplicate-comments.yml | 30 minutes |
Sources: .github/workflows/auto-close-duplicates.yml11 .github/workflows/backfill-duplicate-comments.yml24
Refresh this wiki