mirror of
https://github.com/anthropics/claude-code.git
synced 2026-04-21 03:12:43 +00:00
Compare commits
15 Commits
claude/sla
...
v2.1.50
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ad3231f0f | ||
|
|
65dfa9898e | ||
|
|
0d996a7c34 | ||
|
|
b18f2e7df0 | ||
|
|
b757fc9ecd | ||
|
|
1718a57495 | ||
|
|
4523c004dd | ||
|
|
32c7ff2b6e | ||
|
|
d787369919 | ||
|
|
8c09097e8c | ||
|
|
edfb5437a4 | ||
|
|
b374a30699 | ||
|
|
a01a88d5ee | ||
|
|
42c62d73ce | ||
|
|
1b50583382 |
6
.github/workflows/claude-issue-triage.yml
vendored
6
.github/workflows/claude-issue-triage.yml
vendored
@@ -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:
|
||||
|
||||
27
.github/workflows/issue-lifecycle-comment.yml
vendored
Normal file
27
.github/workflows/issue-lifecycle-comment.yml
vendored
Normal 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 }}
|
||||
195
CHANGELOG.md
195
CHANGELOG.md
@@ -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
|
||||
|
||||
@@ -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}"
|
||||
@@ -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()
|
||||
|
||||
38
scripts/issue-lifecycle.ts
Normal file
38
scripts/issue-lifecycle.ts
Normal 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;
|
||||
53
scripts/lifecycle-comment.ts
Normal file
53
scripts/lifecycle-comment.ts
Normal 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}"`);
|
||||
@@ -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"}`);
|
||||
|
||||
Reference in New Issue
Block a user