The sweep job (https://github.com/anthropics/claude-code/actions/runs/21983111029/job/63510453226)
was silently failing when closeExpired tried to comment on a locked issue,
causing a 403 from the GitHub API.
Two issues:
1. closeExpired didn't skip locked issues like markStale already does.
Adding the same `if (issue.locked) continue` guard fixes this.
2. The error was swallowed by `main().catch(console.error)` which logs
to stderr but exits 0, so CI reported success despite the crash.
Replaced the main() wrapper with top-level await so unhandled errors
properly crash the process with a non-zero exit code.
## Test plan
YOLO
* Unify issue lifecycle labeling and sweep into a single system
Consolidate issue triage, stale detection, and lifecycle enforcement into
two components: a Claude-powered triage workflow and a unified sweep script.
Triage workflow changes:
- Add issue_comment trigger so Claude can re-evaluate lifecycle labels when
someone responds to a needs-repro/needs-info issue
- Add concurrency group per issue with cancel-in-progress to avoid pile-up
- Filter out bot comments to prevent sweep/dedupe triggering re-triage
- Hardcode allowed label whitelist to prevent label sprawl (was discovering
labels via gh label list, leading to junk variants like 'needs repro' vs
'needs-repro')
- Replace MCP GitHub server with gh CLI — simpler, no Docker dependency,
chaining is caught by the action so permissions are equivalent
- Add lifecycle labels (needs-repro, needs-info) for bugs missing info
- Add invalid label for off-topic issues (Claude API, billing, etc.)
- Add anti-patterns to prevent false positives (don't require specific
format, model behavior issues don't need traditional repro, etc.)
Sweep script changes:
- Absorb stale issue detection (was separate stale-issue-manager workflow)
- Mark issues as stale after 14 days of inactivity
- Skip assigned issues (team is working on it internally)
- Skip enhancements with 10+ thumbs up (community wants it)
- Add invalid label with 3-day timeout
- Add autoclose label support to drain 200+ legacy issues
- Drop needs-votes (stale handles inactive enhancements)
- Unify close messages into a single template with per-label reasons
- Run 2x daily instead of once
Delete stale-issue-manager.yml — its logic is now in sweep.ts.
## Test plan
Dry-run sweep locally:
GITHUB_TOKEN=$(gh auth token) GITHUB_REPOSITORY_OWNER=anthropics GITHUB_REPOSITORY_NAME=claude-code bun run scripts/sweep.ts --dry-run
Triage workflow will be tested by opening a test issue after merge.
* Update .github/workflows/claude-issue-triage.yml
Co-authored-by: Ashwin Bhat <ashwin@anthropic.com>
---------
Co-authored-by: Ashwin Bhat <ashwin@anthropic.com>
Introduce a simple, mechanical daily sweep that closes issues with
lifecycle labels past their timeout:
- needs-repro: 7 days
- needs-info: 7 days
- needs-votes: 30 days
- stale: 30 days
The sweep checks when the label was last applied via the events API,
and closes the issue if the timeout has elapsed. No AI, no comment
checking — if the label is still there past its timeout, close it.
Removing a label (by a triager, slash command, or future AI retriage)
is what prevents closure.
Each close message directs the reporter to open a new issue rather
than engaging with the closed one.
The script supports --dry-run for local testing:
GITHUB_TOKEN=$(gh auth token) \
GITHUB_REPOSITORY_OWNER=anthropics \
GITHUB_REPOSITORY_NAME=claude-code \
bun run scripts/sweep.ts --dry-run
## Test plan
Ran --dry-run against anthropics/claude-code. Correctly identified 3
issues past their timeouts (1 needs-repro at 12d, 2 needs-info at
14d and 26d). No false positives.