spacehub

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

Post a comment when lifecycle labels are applied to issuesBrowse files
When lifecycle labels (needs-info, needs-repro, invalid, stale, autoclose)
are applied to an issue, the author currently only sees a label change with
no explanation. They then get a closing comment days later without ever
being nudged to respond.

Add a GitHub Actions workflow that triggers on issues.labeled and runs a
new lifecycle-comment.ts script to post a comment explaining what's needed
and how long before auto-close.

Extract lifecycle config (labels, timeouts, close reasons, nudge messages)
into a shared issue-lifecycle.ts so the sweep script and comment script
stay in sync. Previously the timeouts were duplicated between the sweep
script and the comment messages.

- needs-info: asks for version, OS, error messages
- needs-repro: asks for steps to trigger the issue
- invalid: links to the Claude Code repo and Anthropic support
- stale/autoclose: explains inactivity auto-close

The script no-ops for non-lifecycle labels, so the workflow fires on every
label event and lets the script decide — single source of truth.

## Test plan

Dry-run all labels locally:
GITHUB_REPOSITORY=anthropics/claude-code LABEL=needs-info ISSUE_NUMBER=12345 bun run scripts/lifecycle-comment.ts --dry-run
GITHUB_REPOSITORY=anthropics/claude-code LABEL=needs-repro ISSUE_NUMBER=12345 bun run scripts/lifecycle-comment.ts --dry-run
GITHUB_REPOSITORY=anthropics/claude-code LABEL=invalid ISSUE_NUMBER=12345 bun run scripts/lifecycle-comment.ts --dry-run
GITHUB_REPOSITORY=anthropics/claude-code LABEL=stale ISSUE_NUMBER=12345 bun run scripts/lifecycle-comment.ts --dry-run
GITHUB_REPOSITORY=anthropics/claude-code LABEL=autoclose ISSUE_NUMBER=12345 bun run scripts/lifecycle-comment.ts --dry-run

Verified sweep.ts still works:
GITHUB_TOKEN=$(gh auth token) GITHUB_REPOSITORY_OWNER=anthropics GITHUB_REPOSITORY_NAME=claude-code bun run scripts/sweep.ts --dry-run
Chris Lloyd authored on Feb 14, 2026 parent edfb543 · commit 840464fb1623
4 changed files+123 -12compared with edfb543
A.github/workflows/issue-lifecycle-comment.yml
Ascripts/issue-lifecycle.ts
Ascripts/lifecycle-comment.ts
Mscripts/sweep.ts
@@ -1,0 +1,27 @@
1+name: "Issue Lifecycle Comment"
2+
3+on:
4+ issues:
5+ types: [labeled]
6+
7+permissions:
8+ issues: write
9+
10+jobs:
11+ comment:
12+ runs-on: ubuntu-latest
13+ steps:
14+ - name: Checkout repository
15+ uses: actions/checkout@v4
16+
17+ - name: Setup Bun
18+ uses: oven-sh/setup-bun@v2
19+ with:
20+ bun-version: latest
21+
22+ - name: Post lifecycle comment
23+ run: bun run scripts/lifecycle-comment.ts
24+ env:
25+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
26+ LABEL: ${{ github.event.label.name }}
27+ ISSUE_NUMBER: ${{ github.event.issue.number }}
@@ -1,0 +1,38 @@
1+// Single source of truth for issue lifecycle labels, timeouts, and messages.
2+
3+export const lifecycle = [
4+ {
5+ label: "invalid",
6+ days: 3,
7+ reason: "this doesn't appear to be about Claude Code",
8+ nudge: "This doesn't appear to be about [Claude Code](https://github.com/anthropics/claude-code). For general Anthropic support, visit [support.anthropic.com](https://support.anthropic.com).",
9+ },
10+ {
11+ label: "needs-repro",
12+ days: 7,
13+ reason: "we still need reproduction steps to investigate",
14+ nudge: "We weren't able to reproduce this. Could you provide steps to trigger the issue — what you ran, what happened, and what you expected?",
15+ },
16+ {
17+ label: "needs-info",
18+ days: 7,
19+ reason: "we still need a bit more information to move forward",
20+ nudge: "We need more information to continue investigating. Can you make sure to include your Claude Code version (`claude --version`), OS, and any error messages or logs?",
21+ },
22+ {
23+ label: "stale",
24+ days: 14,
25+ reason: "inactive for too long",
26+ nudge: "This issue has been automatically marked as stale due to inactivity.",
27+ },
28+ {
29+ label: "autoclose",
30+ days: 14,
31+ reason: "inactive for too long",
32+ nudge: "This issue has been marked for automatic closure.",
33+ },
34+] as const;
35+
36+export type LifecycleLabel = (typeof lifecycle)[number]["label"];
37+
38+export const STALE_UPVOTE_THRESHOLD = 10;
@@ -1,0 +1,53 @@
1+#!/usr/bin/env bun
2+
3+// Posts a comment when a lifecycle label is applied to an issue,
4+// giving the author a heads-up and a chance to respond before auto-close.
5+
6+import { lifecycle } from "./issue-lifecycle.ts";
7+
8+const DRY_RUN = process.argv.includes("--dry-run");
9+const token = process.env.GITHUB_TOKEN;
10+const repo = process.env.GITHUB_REPOSITORY; // owner/repo
11+const label = process.env.LABEL;
12+const issueNumber = process.env.ISSUE_NUMBER;
13+
14+if (!DRY_RUN && !token) throw new Error("GITHUB_TOKEN required");
15+if (!repo) throw new Error("GITHUB_REPOSITORY required");
16+if (!label) throw new Error("LABEL required");
17+if (!issueNumber) throw new Error("ISSUE_NUMBER required");
18+
19+const entry = lifecycle.find((l) => l.label === label);
20+if (!entry) {
21+ console.log(`No lifecycle entry for label "${label}", skipping`);
22+ process.exit(0);
23+}
24+
25+const body = `${entry.nudge} This issue will be closed automatically if there's no activity within ${entry.days} days.`;
26+
27+// --
28+
29+if (DRY_RUN) {
30+ console.log(`Would comment on #${issueNumber} for label "${label}":\n\n${body}`);
31+ process.exit(0);
32+}
33+
34+const response = await fetch(
35+ `https://api.github.com/repos/${repo}/issues/${issueNumber}/comments`,
36+ {
37+ method: "POST",
38+ headers: {
39+ Authorization: `Bearer ${token}`,
40+ Accept: "application/vnd.github.v3+json",
41+ "Content-Type": "application/json",
42+ "User-Agent": "lifecycle-comment",
43+ },
44+ body: JSON.stringify({ body }),
45+ }
46+);
47+
48+if (!response.ok) {
49+ const text = await response.text();
50+ throw new Error(`GitHub API ${response.status}: ${text}`);
51+}
52+
53+console.log(`Commented on #${issueNumber} for label "${label}"`);
@@ -1,23 +1,15 @@
11 #!/usr/bin/env bun
22
3+import { lifecycle, STALE_UPVOTE_THRESHOLD } from "./issue-lifecycle.ts";
4+
35 // --
46
57 const NEW_ISSUE = "https://github.com/anthropics/claude-code/issues/new/choose";
68 const DRY_RUN = process.argv.includes("--dry-run");
7-const STALE_DAYS = 14;
8-const STALE_UPVOTE_THRESHOLD = 10;
99
1010 const CLOSE_MESSAGE = (reason: string) =>
1111 `Closing for now — ${reason}. Please [open a new issue](${NEW_ISSUE}) if this is still relevant.`;
1212
13-const lifecycle = [
14- { label: "invalid", days: 3, reason: "this doesn't appear to be about Claude Code" },
15- { label: "needs-repro", days: 7, reason: "we still need reproduction steps to investigate" },
16- { label: "needs-info", days: 7, reason: "we still need a bit more information to move forward" },
17- { label: "stale", days: 14, reason: "inactive for too long" },
18- { label: "autoclose", days: 14, reason: "inactive for too long" },
19-];
20-
2113 // --
2214
2315 async function githubRequest<T>(
@@ -51,12 +43,13 @@
5143 // --
5244
5345 async function markStale(owner: string, repo: string) {
46+ const staleDays = lifecycle.find((l) => l.label === "stale")!.days;
5447 const cutoff = new Date();
55- cutoff.setDate(cutoff.getDate() - STALE_DAYS);
48+ cutoff.setDate(cutoff.getDate() - staleDays);
5649
5750 let labeled = 0;
5851
59- console.log(`\n=== marking stale (${STALE_DAYS}d inactive) ===`);
52+ console.log(`\n=== marking stale (${staleDays}d inactive) ===`);
6053
6154 for (let page = 1; page <= 10; page++) {
6255 const issues = await githubRequest<any[]>(