Compare commits

...

8 Commits

Author SHA1 Message Date
GitHub Actions
d787369919 chore: Update CHANGELOG.md 2026-02-16 21:34:15 +00:00
Chris Lloyd
8c09097e8c Post a comment when lifecycle labels are applied to issues (#25665)
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
2026-02-13 19:39:10 -08:00
Chris Lloyd
edfb5437a4 Fix sweep script crashing on locked issues (#25649)
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
2026-02-13 15:40:00 -08:00
GitHub Actions
b374a30699 chore: Update CHANGELOG.md 2026-02-13 20:01:23 +00:00
GitHub Actions
a01a88d5ee chore: Update CHANGELOG.md 2026-02-13 19:55:44 +00:00
GitHub Actions
42c62d73ce chore: Update CHANGELOG.md 2026-02-13 17:43:37 +00:00
GitHub Actions
1b50583382 chore: Update CHANGELOG.md 2026-02-13 06:07:54 +00:00
GitHub Actions
232213304d chore: Update CHANGELOG.md 2026-02-12 21:17:39 +00:00
5 changed files with 168 additions and 31 deletions

View File

@@ -0,0 +1,27 @@
name: "Issue Lifecycle Comment"
on:
issues:
types: [labeled]
permissions:
issues: write
jobs:
comment:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Post lifecycle comment
run: bun run scripts/lifecycle-comment.ts
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
LABEL: ${{ github.event.label.name }}
ISSUE_NUMBER: ${{ github.event.issue.number }}

View File

@@ -1,6 +1,24 @@
# Changelog
## 2.1.39
## 2.1.44
- Fixed auth refresh errors
## 2.1.43
- Fixed AWS auth refresh hanging indefinitely by adding a 3-minute timeout
- Fixed spurious warnings for non-agent markdown files in `.claude/agents/` directory
- Fixed structured-outputs beta header being sent unconditionally on Vertex/Bedrock
## 2.1.42
- Improved startup performance by deferring Zod schema construction
- Improved prompt cache hit rates by moving date out of system prompt
- Added one-time Opus 4.6 effort callout for eligible users
- Fixed /resume showing interrupt messages as session titles
- Fixed image dimension limit errors to suggest /compact
## 2.1.41
- Added guard against launching Claude Code inside another Claude Code session
- Fixed Agent Teams using wrong model identifier for Bedrock, Vertex, and Foundry customers
@@ -10,10 +28,23 @@
- Fixed plugin browse showing misleading "Space to Toggle" hint for already-installed plugins
- Fixed hook blocking errors (exit code 2) not showing stderr to the user
- Added `speed` attribute to OTel events and trace spans for fast mode visibility
- Fixed /resume showing interrupt messages as session titles
- Fixed Opus 4.6 launch announcement showing for Bedrock/Vertex/Foundry users
- Improved error message for many-image dimension limit errors with /compact suggestion
- Fixed structured-outputs beta header being sent unconditionally on Vertex/Bedrock
- Added `claude auth login`, `claude auth status`, and `claude auth logout` CLI subcommands
- Added Windows ARM64 (win32-arm64) native binary support
- Improved `/rename` to auto-generate session name from conversation context when called without arguments
- Improved narrow terminal layout for prompt footer
- Fixed file resolution failing for @-mentions with anchor fragments (e.g., `@README.md#installation`)
- Fixed FileReadTool blocking the process on FIFOs, `/dev/stdin`, and large files
- Fixed background task notifications not being delivered in streaming Agent SDK mode
- Fixed cursor jumping to end on each keystroke in classifier rule input
- Fixed markdown link display text being dropped for raw URL
- Fixed auto-compact failure error notifications being shown to users
- Fixed permission wait time being included in subagent elapsed time display
- Fixed proactive ticks firing while in plan mode
- Fixed clear stale permission rules when settings change on disk
- Fixed hook blocking errors showing stderr content in UI
## 2.1.39
- Improved terminal rendering performance
- Fixed fatal errors being swallowed instead of displayed
- Fixed process hanging after session close

View File

@@ -0,0 +1,38 @@
// Single source of truth for issue lifecycle labels, timeouts, and messages.
export const lifecycle = [
{
label: "invalid",
days: 3,
reason: "this doesn't appear to be about Claude Code",
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).",
},
{
label: "needs-repro",
days: 7,
reason: "we still need reproduction steps to investigate",
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?",
},
{
label: "needs-info",
days: 7,
reason: "we still need a bit more information to move forward",
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?",
},
{
label: "stale",
days: 14,
reason: "inactive for too long",
nudge: "This issue has been automatically marked as stale due to inactivity.",
},
{
label: "autoclose",
days: 14,
reason: "inactive for too long",
nudge: "This issue has been marked for automatic closure.",
},
] as const;
export type LifecycleLabel = (typeof lifecycle)[number]["label"];
export const STALE_UPVOTE_THRESHOLD = 10;

View File

@@ -0,0 +1,53 @@
#!/usr/bin/env bun
// Posts a comment when a lifecycle label is applied to an issue,
// giving the author a heads-up and a chance to respond before auto-close.
import { lifecycle } from "./issue-lifecycle.ts";
const DRY_RUN = process.argv.includes("--dry-run");
const token = process.env.GITHUB_TOKEN;
const repo = process.env.GITHUB_REPOSITORY; // owner/repo
const label = process.env.LABEL;
const issueNumber = process.env.ISSUE_NUMBER;
if (!DRY_RUN && !token) throw new Error("GITHUB_TOKEN required");
if (!repo) throw new Error("GITHUB_REPOSITORY required");
if (!label) throw new Error("LABEL required");
if (!issueNumber) throw new Error("ISSUE_NUMBER required");
const entry = lifecycle.find((l) => l.label === label);
if (!entry) {
console.log(`No lifecycle entry for label "${label}", skipping`);
process.exit(0);
}
const body = `${entry.nudge} This issue will be closed automatically if there's no activity within ${entry.days} days.`;
// --
if (DRY_RUN) {
console.log(`Would comment on #${issueNumber} for label "${label}":\n\n${body}`);
process.exit(0);
}
const response = await fetch(
`https://api.github.com/repos/${repo}/issues/${issueNumber}/comments`,
{
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
Accept: "application/vnd.github.v3+json",
"Content-Type": "application/json",
"User-Agent": "lifecycle-comment",
},
body: JSON.stringify({ body }),
}
);
if (!response.ok) {
const text = await response.text();
throw new Error(`GitHub API ${response.status}: ${text}`);
}
console.log(`Commented on #${issueNumber} for label "${label}"`);

View File

@@ -1,23 +1,15 @@
#!/usr/bin/env bun
import { lifecycle, STALE_UPVOTE_THRESHOLD } from "./issue-lifecycle.ts";
// --
const NEW_ISSUE = "https://github.com/anthropics/claude-code/issues/new/choose";
const DRY_RUN = process.argv.includes("--dry-run");
const STALE_DAYS = 14;
const STALE_UPVOTE_THRESHOLD = 10;
const CLOSE_MESSAGE = (reason: string) =>
`Closing for now — ${reason}. Please [open a new issue](${NEW_ISSUE}) if this is still relevant.`;
const lifecycle = [
{ label: "invalid", days: 3, reason: "this doesn't appear to be about Claude Code" },
{ label: "needs-repro", days: 7, reason: "we still need reproduction steps to investigate" },
{ label: "needs-info", days: 7, reason: "we still need a bit more information to move forward" },
{ label: "stale", days: 14, reason: "inactive for too long" },
{ label: "autoclose", days: 14, reason: "inactive for too long" },
];
// --
async function githubRequest<T>(
@@ -51,12 +43,13 @@ async function githubRequest<T>(
// --
async function markStale(owner: string, repo: string) {
const staleDays = lifecycle.find((l) => l.label === "stale")!.days;
const cutoff = new Date();
cutoff.setDate(cutoff.getDate() - STALE_DAYS);
cutoff.setDate(cutoff.getDate() - staleDays);
let labeled = 0;
console.log(`\n=== marking stale (${STALE_DAYS}d inactive) ===`);
console.log(`\n=== marking stale (${staleDays}d inactive) ===`);
for (let page = 1; page <= 10; page++) {
const issues = await githubRequest<any[]>(
@@ -115,6 +108,7 @@ async function closeExpired(owner: string, repo: string) {
for (const issue of issues) {
if (issue.pull_request) continue;
if (issue.locked) continue;
const base = `/repos/${owner}/${repo}/issues/${issue.number}`;
const events = await githubRequest<any[]>(`${base}/events?per_page=100`);
@@ -144,20 +138,14 @@ async function closeExpired(owner: string, repo: string) {
// --
async function main() {
const owner = process.env.GITHUB_REPOSITORY_OWNER;
const repo = process.env.GITHUB_REPOSITORY_NAME;
if (!owner || !repo)
throw new Error("GITHUB_REPOSITORY_OWNER and GITHUB_REPOSITORY_NAME required");
const owner = process.env.GITHUB_REPOSITORY_OWNER;
const repo = process.env.GITHUB_REPOSITORY_NAME;
if (!owner || !repo)
throw new Error("GITHUB_REPOSITORY_OWNER and GITHUB_REPOSITORY_NAME required");
if (DRY_RUN) console.log("DRY RUN — no changes will be made\n");
if (DRY_RUN) console.log("DRY RUN — no changes will be made\n");
const labeled = await markStale(owner, repo);
const closed = await closeExpired(owner, repo);
const labeled = await markStale(owner, repo);
const closed = await closeExpired(owner, repo);
console.log(`\nDone: ${labeled} ${DRY_RUN ? "would be labeled" : "labeled"} stale, ${closed} ${DRY_RUN ? "would be closed" : "closed"}`);
}
main().catch(console.error);
export {};
console.log(`\nDone: ${labeled} ${DRY_RUN ? "would be labeled" : "labeled"} stale, ${closed} ${DRY_RUN ? "would be closed" : "closed"}`);