Compare commits

..

15 Commits

Author SHA1 Message Date
GitHub Actions
3ad3231f0f chore: Update CHANGELOG.md 2026-02-20 23:48:06 +00:00
GitHub Actions
65dfa9898e chore: Update CHANGELOG.md 2026-02-20 15:26:36 +00:00
GitHub Actions
0d996a7c34 chore: Update CHANGELOG.md 2026-02-19 23:55:04 +00:00
GitHub Actions
b18f2e7df0 chore: Update CHANGELOG.md
Fixes #26637
2026-02-19 23:27:35 +00:00
GitHub Actions
b757fc9ecd chore: Update CHANGELOG.md
Fixes #31
Fixes #17600
Fixes #22020
Fixes #23610
Fixes #25721
Fixes #25748
Fixes #25750
Fixes #25756
Fixes #25789
Fixes #25792
Fixes #25803
Fixes #25811
Fixes #25816
Fixes #25824
Fixes #25826
Fixes #25834
Fixes #25837
Fixes #25909
Fixes #25920
Fixes #25935
Fixes #25943
Fixes #25957
Fixes #25981
Fixes #25990
Fixes #26012
Fixes #26023
Fixes #26033
Fixes #26044
Fixes #26051
Fixes #26059
Fixes #26061
Fixes #26064
Fixes #26065
Fixes #26074
Fixes #26075
Fixes #26078
Fixes #26082
Fixes #26084
Fixes #26096
Fixes #26105
Fixes #26121
Fixes #26123
Fixes #26130
Fixes #26136
Fixes #26140
Fixes #26141
Fixes #26188
2026-02-18 21:37:52 +00:00
Chris Lloyd
1718a57495 Fix issues being auto-closed despite human activity (#26360)
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>
2026-02-17 10:53:20 -08:00
GitHub Actions
4523c004dd chore: Update CHANGELOG.md
Fixes #21654
Fixes #22087
Fixes #23561
2026-02-17 18:53:01 +00:00
GitHub Actions
32c7ff2b6e chore: Update CHANGELOG.md 2026-02-17 13:21:46 +00:00
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
8 changed files with 351 additions and 265 deletions

View File

@@ -45,7 +45,7 @@ jobs:
ALLOWED LABELS — you may ONLY use labels from this list. Never invent new labels.
Type: bug, enhancement, question, documentation, duplicate, invalid
Lifecycle: needs-repro, needs-info
Lifecycle: needs-repro, needs-info, stale, autoclose
Platform: platform:linux, platform:macos, platform:windows, platform:wsl, platform:ios, platform:android, platform:vscode, platform:intellij, platform:web, platform:aws-bedrock
API: api:bedrock, api:vertex
@@ -85,10 +85,12 @@ jobs:
**If EVENT is "issue_comment" (comment on existing issue):**
3. Evaluate lifecycle labels based on the full conversation:
- If the issue has `stale` or `autoclose`, remove the label — a new human comment means the issue is still active:
`gh issue edit ${{ github.event.issue.number }} --remove-label "stale" --remove-label "autoclose"`
- If the issue has `needs-repro` or `needs-info` and the missing information has now been provided, remove the label:
`gh issue edit ${{ github.event.issue.number }} --remove-label "needs-repro"`
- 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.
- Comments like "+1", "me too", "same here", or emoji reactions are NOT the missing information. Only remove labels when substantive details are actually provided.
- 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.
- Do NOT add or remove category labels (bug, enhancement, etc.) on comment events.
GUIDELINES:

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,177 @@
# Changelog
## 2.1.39
## 2.1.50
- Added support for `startupTimeout` configuration for LSP servers
- Added `WorktreeCreate` and `WorktreeRemove` hook events, enabling custom VCS setup and teardown when agent worktree isolation creates or removes worktrees.
- Fixed a bug where resumed sessions could be invisible when the working directory involved symlinks, because the session storage path was resolved at different times during startup. Also fixed session data loss on SSH disconnect by flushing session data before hooks and analytics in the graceful shutdown sequence.
- Linux: Fixed native modules not loading on systems with glibc older than 2.30 (e.g., RHEL 8)
- Fixed memory leak in agent teams where completed teammate tasks were never garbage collected from session state
- Fixed `CLAUDE_CODE_SIMPLE` to fully strip down skills, session memory, custom agents, and CLAUDE.md token counting
- Fixed `/mcp reconnect` freezing the CLI when given a server name that doesn't exist
- Fixed memory leak where completed task state objects were never removed from AppState
- Added support for `isolation: worktree` in agent definitions, allowing agents to declaratively run in isolated git worktrees.
- `CLAUDE_CODE_SIMPLE` mode now also disables MCP tools, attachments, hooks, and CLAUDE.md file loading for a fully minimal experience.
- Fixed bug where MCP tools were not discovered when tool search is enabled and a prompt is passed in as a launch argument
- Improved memory usage during long sessions by clearing internal caches after compaction
- Added `claude agents` CLI command to list all configured agents
- Improved memory usage during long sessions by clearing large tool results after they have been processed
- Fixed a memory leak where LSP diagnostic data was never cleaned up after delivery, causing unbounded memory growth in long sessions
- Fixed a memory leak where completed task output was not freed from memory, reducing memory usage in long sessions with many tasks
- Improved startup performance for headless mode (`-p` flag) by deferring Yoga WASM and UI component imports
- Fixed prompt suggestion cache regression that reduced cache hit rates
- Fixed unbounded memory growth in long sessions by capping file history snapshots
- Added `CLAUDE_CODE_DISABLE_1M_CONTEXT` environment variable to disable 1M context window support
- Opus 4.6 (fast mode) now includes the full 1M context window
- VSCode: Added `/extra-usage` command support in VS Code sessions
- Fixed memory leak where TaskOutput retained recent lines after cleanup
- Fixed memory leak in CircularBuffer where cleared items were retained in the backing array
- Fixed memory leak in shell command execution where ChildProcess and AbortController references were retained after cleanup
## 2.1.49
- Improved MCP OAuth authentication with step-up auth support and discovery caching, reducing redundant network requests during server connections
- Added `--worktree` (`-w`) flag to start Claude in an isolated git worktree
- Subagents support `isolation: "worktree"` for working in a temporary git worktree
- Added Ctrl+F keybinding to kill background agents (two-press confirmation)
- Agent definitions support `background: true` to always run as a background task
- Plugins can ship `settings.json` for default configuration
- Fixed file-not-found errors to suggest corrected paths when the model drops the repo folder
- Fixed Ctrl+C and ESC being silently ignored when background agents are running and the main thread is idle. Pressing twice within 3 seconds now kills all background agents.
- Fixed prompt suggestion cache regression that reduced cache hit rates.
- Fixed `plugin enable` and `plugin disable` to auto-detect the correct scope when `--scope` is not specified, instead of always defaulting to user scope
- Simple mode (`CLAUDE_CODE_SIMPLE`) now includes the file edit tool in addition to the Bash tool, allowing direct file editing in simple mode.
- Permission suggestions are now populated when safety checks trigger an ask response, enabling SDK consumers to display permission options
- Sonnet 4.5 with 1M context is being removed from the Max plan in favor of our frontier Sonnet 4.6 model, which now has 1M context. Please switch in /model.
- Fixed verbose mode not updating thinking block display when toggled via `/config` — memo comparators now correctly detect verbose changes
- Fixed unbounded WASM memory growth during long sessions by periodically resetting the tree-sitter parser
- Fixed potential rendering issues caused by stale yoga layout references
- Improved performance in non-interactive mode (`-p`) by skipping unnecessary API calls during startup
- Improved performance by caching authentication failures for HTTP and SSE MCP servers, avoiding repeated connection attempts to servers requiring auth
- Fixed unbounded memory growth during long-running sessions caused by Yoga WASM linear memory never shrinking
- SDK model info now includes `supportsEffort`, `supportedEffortLevels`, and `supportsAdaptiveThinking` fields so consumers can discover model capabilities.
- Added `ConfigChange` hook event that fires when configuration files change during a session, enabling enterprise security auditing and optional blocking of settings changes.
- Improved startup performance by caching MCP auth failures to avoid redundant connection attempts
- Improved startup performance by reducing HTTP calls for analytics token counting
- Improved startup performance by batching MCP tool token counting into a single API call
- Fixed `disableAllHooks` setting to respect managed settings hierarchy — non-managed settings can no longer disable managed hooks set by policy (#26637)
- Fixed `--resume` session picker showing raw XML tags for sessions that start with commands like `/clear`. Now correctly falls through to the session ID fallback.
- Improved permission prompts for path safety and working directory blocks to show the reason for the restriction instead of a bare prompt with no context
## 2.1.47
- Fixed FileWriteTool line counting to preserve intentional trailing blank lines instead of stripping them with `trimEnd()`.
- Fixed Windows terminal rendering bugs caused by `os.EOL` (`\r\n`) in display code — line counts now show correct values instead of always showing 1 on Windows.
- Improved VS Code plan preview: auto-updates as Claude iterates, enables commenting only when the plan is ready for review, and keeps the preview open when rejecting so Claude can revise.
- Fixed a bug where bold and colored text in markdown output could shift to the wrong characters on Windows due to `\r\n` line endings.
- Fixed compaction failing when conversation contains many PDF documents by stripping document blocks alongside images before sending to the compaction API (anthropics/claude-code#26188)
- Improved memory usage in long-running sessions by releasing API stream buffers, agent context, and skill state after use
- Improved startup performance by deferring SessionStart hook execution, reducing time-to-interactive by ~500ms.
- Fixed an issue where bash tool output was silently discarded on Windows when using MSYS2 or Cygwin shells.
- Improved performance of `@` file mentions - file suggestions now appear faster by pre-warming the index on startup and using session-based caching with background refresh.
- Improved memory usage by trimming agent task message history after tasks complete
- Improved memory usage during long agent sessions by eliminating O(n²) message accumulation in progress updates
- Fixed the bash permission classifier to validate that returned match descriptions correspond to actual input rules, preventing hallucinated descriptions from incorrectly granting permissions
- Fixed user-defined agents only loading one file on NFS/FUSE filesystems that report zero inodes (anthropics/claude-code#26044)
- Fixed plugin agent skills silently failing to load when referenced by bare name instead of fully-qualified plugin name (anthropics/claude-code#25834)
- Search patterns in collapsed tool results are now displayed in quotes for clarity
- Windows: Fixed CWD tracking temp files never being cleaned up, causing them to accumulate indefinitely (anthropics/claude-code#17600)
- Use `ctrl+f` to kill all background agents instead of double-pressing ESC. Background agents now continue running when you press ESC to cancel the main thread, giving you more control over agent lifecycle.
- Fixed API 400 errors ("thinking blocks cannot be modified") that occurred in sessions with concurrent agents, caused by interleaved streaming content blocks preventing proper message merging.
- Simplified teammate navigation to use only Shift+Down (with wrapping) instead of both Shift+Up and Shift+Down.
- Fixed an issue where a single file write/edit error would abort all other parallel file write/edit operations. Independent file mutations now complete even when a sibling fails.
- Added `last_assistant_message` field to Stop and SubagentStop hook inputs, providing the final assistant response text so hooks can access it without parsing transcript files.
- Fixed custom session titles set via `/rename` being lost after resuming a conversation (anthropics/claude-code#23610)
- Fixed collapsed read/search hint text overflowing on narrow terminals by truncating from the start.
- Fixed an issue where bash commands with backslash-newline continuation lines (e.g., long commands split across multiple lines with `\`) would produce spurious empty arguments, potentially breaking command execution.
- Fixed built-in slash commands (`/help`, `/model`, `/compact`, etc.) being hidden from the autocomplete dropdown when many user skills are installed (anthropics/claude-code#22020)
- Fixed MCP servers not appearing in the MCP Management Dialog after deferred loading
- Fixed session name persisting in status bar after `/clear` command (anthropics/claude-code#26082)
- Fixed crash when a skill's `name` or `description` in SKILL.md frontmatter is a bare number (e.g., `name: 3000`) — the value is now properly coerced to a string (anthropics/claude-code#25837)
- Fixed /resume silently dropping sessions when the first message exceeds 16KB or uses array-format content (anthropics/claude-code#25721)
- Added `chat:newline` keybinding action for configurable multi-line input (anthropics/claude-code#26075)
- Added `added_dirs` to the statusline JSON `workspace` section, exposing directories added via `/add-dir` to external scripts (anthropics/claude-code#26096)
- Fixed `claude doctor` misclassifying mise and asdf-managed installations as native installs (anthropics/claude-code#26033)
- Fixed zsh heredoc failing with "read-only file system" error in sandboxed commands (anthropics/claude-code#25990)
- Fixed agent progress indicator showing inflated tool use count (anthropics/claude-code#26023)
- Fixed image pasting not working on WSL2 systems where Windows copies images as BMP format (anthropics/claude-code#25935)
- Fixed background agent results returning raw transcript data instead of the agent's final answer (anthropics/claude-code#26012)
- Fixed Warp terminal incorrectly prompting for Shift+Enter setup when it supports it natively (anthropics/claude-code#25957)
- Fixed CJK wide characters causing misaligned timestamps and layout elements in the TUI (anthropics/claude-code#26084)
- Fixed custom agent `model` field in `.claude/agents/*.md` being ignored when spawning team teammates (anthropics/claude-code#26064)
- Fixed plan mode being lost after context compaction, causing the model to switch from planning to implementation mode (anthropics/claude-code#26061)
- Fixed `alwaysThinkingEnabled: true` in settings.json not enabling thinking mode on Bedrock and Vertex providers (anthropics/claude-code#26074)
- Fixed `tool_decision` OTel telemetry event not being emitted in headless/SDK mode (anthropics/claude-code#26059)
- Fixed session name being lost after context compaction — renamed sessions now preserve their custom title through compaction (anthropics/claude-code#26121)
- Increased initial session count in resume picker from 10 to 50 for faster session discovery (anthropics/claude-code#26123)
- Windows: fixed worktree session matching when drive letter casing differs (anthropics/claude-code#26123)
- Fixed `/resume <session-id>` failing to find sessions whose first message exceeds 16KB (anthropics/claude-code#25920)
- Fixed "Always allow" on multiline bash commands creating invalid permission patterns that corrupt settings (anthropics/claude-code#25909)
- Fixed React crash (error #31) when a skill's `argument-hint` in SKILL.md frontmatter uses YAML sequence syntax (e.g., `[topic: foo | bar]`) — the value is now properly coerced to a string (anthropics/claude-code#25826)
- Fixed crash when using `/fork` on sessions that used web search — null entries in search results from transcript deserialization are now handled gracefully (anthropics/claude-code#25811)
- Fixed read-only git commands triggering FSEvents file watcher loops on macOS by adding --no-optional-locks flag (anthropics/claude-code#25750)
- Fixed custom agents and skills not being discovered when running from a git worktree — project-level `.claude/agents/` and `.claude/skills/` from the main repository are now included (anthropics/claude-code#25816)
- Fixed non-interactive subcommands like `claude doctor` and `claude plugin validate` being blocked inside nested Claude sessions (anthropics/claude-code#25803)
- Windows: Fixed the same CLAUDE.md file being loaded twice when drive letter casing differs between paths (anthropics/claude-code#25756)
- Fixed inline code spans in markdown being incorrectly parsed as bash commands (anthropics/claude-code#25792)
- Fixed teammate spinners not respecting custom spinnerVerbs from settings (anthropics/claude-code#25748)
- Fixed shell commands permanently failing after a command deletes its own working directory (anthropics/claude-code#26136)
- Fixed hooks (PreToolUse, PostToolUse) silently failing to execute on Windows by using Git Bash instead of cmd.exe (anthropics/claude-code#25981)
- Fixed LSP `findReferences` and other location-based operations returning results from gitignored files (e.g., `node_modules/`, `venv/`) (anthropics/claude-code#26051)
- Moved config backup files from home directory root to `~/.claude/backups/` to reduce home directory clutter (anthropics/claude-code#26130)
- Fixed sessions with large first prompts (>16KB) disappearing from the /resume list (anthropics/claude-code#26140)
- Fixed shell functions with double-underscore prefixes (e.g., `__git_ps1`) not being preserved across shell sessions (anthropics/claude-code#25824)
- Fixed spinner showing "0 tokens" counter before any tokens have been received (anthropics/claude-code#26105)
- VSCode: Fixed conversation messages appearing dimmed while the AskUserQuestion dialog is open (anthropics/claude-code#26078)
- Fixed background tasks failing in git worktrees due to remote URL resolution reading from worktree-specific gitdir instead of the main repository config (anthropics/claude-code#26065)
- Fixed Right Alt key leaving visible `[25~` escape sequence residue in the input field on Windows/Git Bash terminals (anthropics/claude-code#25943)
- The `/rename` command now updates the terminal tab title by default (anthropics/claude-code#25789)
- Fixed Edit tool silently corrupting Unicode curly quotes (\u201c\u201d \u2018\u2019) by replacing them with straight quotes when making edits (anthropics/claude-code#26141)
- Fixed OSC 8 hyperlinks only being clickable on the first line when link text wraps across multiple terminal lines.
## 2.1.46
- Fixed orphaned CC processes after terminal disconnect on macOS
- Added support for using claude.ai MCP connectors in Claude Code
## 2.1.45
- Added support for Claude Sonnet 4.6
- Added support for reading `enabledPlugins` and `extraKnownMarketplaces` from `--add-dir` directories
- Added `spinnerTipsOverride` setting to customize spinner tips — configure `tips` with an array of custom tip strings, and optionally set `excludeDefault: true` to show only your custom tips instead of the built-in ones
- Added `SDKRateLimitInfo` and `SDKRateLimitEvent` types to the SDK, enabling consumers to receive rate limit status updates including utilization, reset times, and overage information
- Fixed Agent Teams teammates failing on Bedrock, Vertex, and Foundry by propagating API provider environment variables to tmux-spawned processes (anthropics/claude-code#23561)
- Fixed sandbox "operation not permitted" errors when writing temporary files on macOS by using the correct per-user temp directory (anthropics/claude-code#21654)
- Fixed Task tool (backgrounded agents) crashing with a `ReferenceError` on completion (anthropics/claude-code#22087)
- Fixed autocomplete suggestions not being accepted on Enter when images are pasted in the input
- Fixed skills invoked by subagents incorrectly appearing in main session context after compaction
- Fixed excessive `.claude.json.backup` files accumulating on every startup
- Fixed plugin-provided commands, agents, and hooks not being available immediately after installation without requiring a restart
- Improved startup performance by removing eager loading of session history for stats caching
- Improved memory usage for shell commands that produce large output — RSS no longer grows unboundedly with command output size
- Improved collapsed read/search groups to show the current file or search pattern being processed beneath the summary line while active
- [VSCode] Improved permission destination choice (project/user/session) to persist across sessions
## 2.1.44
- Fixed ENAMETOOLONG errors for deeply-nested directory paths
- 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,11 +181,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
- Fixed spurious warnings for non-agent markdown files in `.claude/agents/` directory
- 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

@@ -1,173 +0,0 @@
#!/usr/bin/env python3
"""
Disk space utilities for Claude Code hooks.
Provides helper functions to detect and handle disk space issues (ENOSPC errors)
in a user-friendly manner.
"""
import errno
import os
import sys
from typing import Optional, Tuple
# ENOSPC errno value (28 on Linux/Mac)
ENOSPC_ERRNO = errno.ENOSPC
def is_disk_space_error(exception: Exception) -> bool:
"""Check if an exception is related to disk space issues.
Args:
exception: The exception to check
Returns:
True if the exception indicates a disk space issue
"""
# Check for OSError with ENOSPC errno
if isinstance(exception, OSError):
if hasattr(exception, 'errno') and exception.errno == ENOSPC_ERRNO:
return True
# Also check strerror for various disk space error messages
if hasattr(exception, 'strerror') and exception.strerror:
strerror_lower = exception.strerror.lower()
disk_space_indicators = [
'no space left on device',
'disk quota exceeded',
'not enough space',
'insufficient disk space',
]
if any(indicator in strerror_lower for indicator in disk_space_indicators):
return True
# Check error message string as fallback
error_str = str(exception).lower()
if 'enospc' in error_str or 'no space left' in error_str:
return True
return False
def get_disk_space_warning() -> str:
"""Get a user-friendly warning message for disk space issues.
Returns:
Warning message string
"""
return (
"WARNING: Disk space issue detected. Your disk may be full or nearly full.\n"
"This can cause Claude Code to become unresponsive or crash.\n"
"\n"
"Recommended actions:\n"
" 1. Free up disk space by deleting unnecessary files\n"
" 2. Check available space with: df -h\n"
" 3. Clean up temporary files: sudo rm -rf /tmp/* (use with caution)\n"
" 4. Empty trash/recycle bin\n"
" 5. Consider removing old Docker images: docker system prune"
)
def check_available_disk_space(path: str = None, min_bytes: int = 10 * 1024 * 1024) -> Tuple[bool, Optional[str]]:
"""Check if there's sufficient disk space available.
Args:
path: Path to check (defaults to home directory)
min_bytes: Minimum required bytes (default: 10MB)
Returns:
Tuple of (has_space, warning_message)
- has_space: True if sufficient space available
- warning_message: Warning string if low on space, None otherwise
"""
if path is None:
path = os.path.expanduser("~")
try:
# Get disk usage statistics
stat = os.statvfs(path)
available_bytes = stat.f_frsize * stat.f_bavail
if available_bytes < min_bytes:
available_mb = available_bytes / (1024 * 1024)
required_mb = min_bytes / (1024 * 1024)
return False, (
f"Low disk space warning: Only {available_mb:.1f}MB available "
f"(recommended minimum: {required_mb:.1f}MB)\n"
f"{get_disk_space_warning()}"
)
return True, None
except (OSError, AttributeError):
# os.statvfs not available on all platforms (e.g., Windows)
# Return True and let actual write operations fail if there's no space
return True, None
def safe_write_file(path: str, content: str, warn_on_disk_error: bool = True) -> Tuple[bool, Optional[str]]:
"""Safely write content to a file with disk space error handling.
Args:
path: Path to write to
content: Content to write
warn_on_disk_error: If True, print warning to stderr on disk space errors
Returns:
Tuple of (success, error_message)
- success: True if write succeeded
- error_message: Error description if failed, None otherwise
"""
try:
# Ensure directory exists
dir_path = os.path.dirname(path)
if dir_path:
os.makedirs(dir_path, exist_ok=True)
with open(path, 'w') as f:
f.write(content)
return True, None
except Exception as e:
if is_disk_space_error(e):
error_msg = f"Disk space error writing to {path}: {e}\n{get_disk_space_warning()}"
if warn_on_disk_error:
print(error_msg, file=sys.stderr)
return False, error_msg
else:
return False, f"Error writing to {path}: {e}"
def safe_append_file(path: str, content: str, warn_on_disk_error: bool = True) -> Tuple[bool, Optional[str]]:
"""Safely append content to a file with disk space error handling.
Args:
path: Path to append to
content: Content to append
warn_on_disk_error: If True, print warning to stderr on disk space errors
Returns:
Tuple of (success, error_message)
- success: True if append succeeded
- error_message: Error description if failed, None otherwise
"""
try:
# Ensure directory exists
dir_path = os.path.dirname(path)
if dir_path:
os.makedirs(dir_path, exist_ok=True)
with open(path, 'a') as f:
f.write(content)
return True, None
except Exception as e:
if is_disk_space_error(e):
error_msg = f"Disk space error appending to {path}: {e}\n{get_disk_space_warning()}"
if warn_on_disk_error:
print(error_msg, file=sys.stderr)
return False, error_msg
else:
return False, f"Error appending to {path}: {e}"

View File

@@ -10,40 +10,18 @@ import random
import sys
from datetime import datetime
# Import disk space utilities
try:
from disk_space_utils import (
is_disk_space_error,
get_disk_space_warning,
check_available_disk_space,
safe_write_file,
safe_append_file,
)
DISK_UTILS_AVAILABLE = True
except ImportError:
# Fallback if disk_space_utils not available
DISK_UTILS_AVAILABLE = False
# Debug log file
DEBUG_LOG_FILE = "/tmp/security-warnings-log.txt"
# Track if we've already warned about disk space in this session
_disk_space_warned = False
def debug_log(message):
"""Append debug message to log file with timestamp."""
global _disk_space_warned
try:
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
with open(DEBUG_LOG_FILE, "a") as f:
f.write(f"[{timestamp}] {message}\n")
except Exception as e:
# Check if this is a disk space error and warn the user
if DISK_UTILS_AVAILABLE and is_disk_space_error(e) and not _disk_space_warned:
_disk_space_warned = True
print(f"[Security Hook] {get_disk_space_warning()}", file=sys.stderr)
# Continue silently to avoid disrupting the hook
# Silently ignore logging errors to avoid disrupting the hook
pass
@@ -180,44 +158,26 @@ def cleanup_old_state_files():
def load_state(session_id):
"""Load the state of shown warnings from file."""
global _disk_space_warned
state_file = get_state_file(session_id)
if os.path.exists(state_file):
try:
with open(state_file, "r") as f:
return set(json.load(f))
except json.JSONDecodeError:
debug_log(f"JSON decode error reading state file: {state_file}")
return set()
except Exception as e:
# Check for disk-related errors (corrupted filesystem, etc.)
if DISK_UTILS_AVAILABLE and is_disk_space_error(e):
if not _disk_space_warned:
_disk_space_warned = True
print(f"[Security Hook] {get_disk_space_warning()}", file=sys.stderr)
debug_log(f"Error loading state file: {e}")
except (json.JSONDecodeError, IOError):
return set()
return set()
def save_state(session_id, shown_warnings):
"""Save the state of shown warnings to file."""
global _disk_space_warned
state_file = get_state_file(session_id)
try:
os.makedirs(os.path.dirname(state_file), exist_ok=True)
with open(state_file, "w") as f:
json.dump(list(shown_warnings), f)
except Exception as e:
# Check for disk space errors and provide user-friendly warning
if DISK_UTILS_AVAILABLE and is_disk_space_error(e):
if not _disk_space_warned:
_disk_space_warned = True
print(f"[Security Hook] {get_disk_space_warning()}", file=sys.stderr)
debug_log(f"Disk space error saving state file: {e}")
else:
debug_log(f"Failed to save state file: {e}")
# Fail silently to not disrupt operation
except IOError as e:
debug_log(f"Failed to save state file: {e}")
pass # Fail silently if we can't save state
def check_patterns(file_path, content):
@@ -256,8 +216,6 @@ def extract_content_from_input(tool_name, tool_input):
def main():
"""Main hook function."""
global _disk_space_warned
# Check if security reminders are enabled
security_reminder_enabled = os.environ.get("ENABLE_SECURITY_REMINDER", "1")
@@ -265,13 +223,6 @@ def main():
if security_reminder_enabled == "0":
sys.exit(0)
# Check for low disk space and warn user (only once per session)
if DISK_UTILS_AVAILABLE and not _disk_space_warned:
has_space, warning = check_available_disk_space()
if not has_space:
_disk_space_warned = True
print(f"[Security Hook] {warning}", file=sys.stderr)
# Periodically clean up old state files (10% chance per run)
if random.random() < 0.1:
cleanup_old_state_files()

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[]>(
@@ -77,11 +70,8 @@ async function markStale(owner: string, repo: string) {
);
if (alreadyStale) continue;
const isEnhancement = issue.labels?.some(
(l: any) => l.name === "enhancement"
);
const thumbsUp = issue.reactions?.["+1"] ?? 0;
if (isEnhancement && thumbsUp >= STALE_UPVOTE_THRESHOLD) continue;
if (thumbsUp >= STALE_UPVOTE_THRESHOLD) continue;
const base = `/repos/${owner}/${repo}/issues/${issue.number}`;
@@ -115,6 +105,11 @@ async function closeExpired(owner: string, repo: string) {
for (const issue of issues) {
if (issue.pull_request) continue;
if (issue.locked) continue;
const thumbsUp = issue.reactions?.["+1"] ?? 0;
if (thumbsUp >= STALE_UPVOTE_THRESHOLD) continue;
const base = `/repos/${owner}/${repo}/issues/${issue.number}`;
const events = await githubRequest<any[]>(`${base}/events?per_page=100`);
@@ -126,6 +121,22 @@ async function closeExpired(owner: string, repo: string) {
if (!labeledAt || labeledAt > cutoff) continue;
// 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.
const comments = await githubRequest<any[]>(
`${base}/comments?since=${labeledAt.toISOString()}&per_page=100`
);
const hasHumanComment = comments.some(
(c) => c.user && c.user.type !== "Bot"
);
if (hasHumanComment) {
console.log(
`#${issue.number}: skipping (human activity after ${label} label)`
);
continue;
}
if (DRY_RUN) {
const age = Math.floor((Date.now() - labeledAt.getTime()) / 86400000);
console.log(`#${issue.number}: would close (${label}, ${age}d old) — ${issue.title}`);
@@ -144,20 +155,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"}`);