spacehub

surshar / claude-code

Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.

Built by Anthropic · github.com/anthropics

Add disk space error handling utilities for better UX when disk is fullBrowse files
When disk space runs out, Claude Code can become unresponsive or crash without
clear feedback. This adds:

- New disk_space_utils.py module with:
  - ENOSPC error detection (errno 28)
  - User-friendly warning messages with remediation steps
  - Disk space availability checking
  - Safe file write/append helpers

- Updated security_reminder_hook.py to:
  - Check disk space at startup and warn users proactively
  - Detect disk space errors during state file operations
  - Provide actionable guidance when disk issues are detected

The warnings include specific remediation steps (df -h, cleaning /tmp,
emptying trash, docker prune) to help users resolve the issue.

Slack context: https://anthropic.slack.com/archives/C07VBSHV7EV/p1770941952212839

https://claude.ai/code/session_017ywHZBHvZasAWS6qcKXCb3
Claude authored on Feb 13, 2026 parent 2322133 · commit 965dbf27ebde
2 changed files+227 -5compared with 2322133
Aplugins/security-guidance/hooks/disk_space_utils.py
Mplugins/security-guidance/hooks/security_reminder_hook.py
@@ -1,0 +1,173 @@
1+#!/usr/bin/env python3
2+"""
3+Disk space utilities for Claude Code hooks.
4+
5+Provides helper functions to detect and handle disk space issues (ENOSPC errors)
6+in a user-friendly manner.
7+"""
8+
9+import errno
10+import os
11+import sys
12+from typing import Optional, Tuple
13+
14+
15+# ENOSPC errno value (28 on Linux/Mac)
16+ENOSPC_ERRNO = errno.ENOSPC
17+
18+
19+def is_disk_space_error(exception: Exception) -> bool:
20+ """Check if an exception is related to disk space issues.
21+
22+ Args:
23+ exception: The exception to check
24+
25+ Returns:
26+ True if the exception indicates a disk space issue
27+ """
28+ # Check for OSError with ENOSPC errno
29+ if isinstance(exception, OSError):
30+ if hasattr(exception, 'errno') and exception.errno == ENOSPC_ERRNO:
31+ return True
32+ # Also check strerror for various disk space error messages
33+ if hasattr(exception, 'strerror') and exception.strerror:
34+ strerror_lower = exception.strerror.lower()
35+ disk_space_indicators = [
36+ 'no space left on device',
37+ 'disk quota exceeded',
38+ 'not enough space',
39+ 'insufficient disk space',
40+ ]
41+ if any(indicator in strerror_lower for indicator in disk_space_indicators):
42+ return True
43+
44+ # Check error message string as fallback
45+ error_str = str(exception).lower()
46+ if 'enospc' in error_str or 'no space left' in error_str:
47+ return True
48+
49+ return False
50+
51+
52+def get_disk_space_warning() -> str:
53+ """Get a user-friendly warning message for disk space issues.
54+
55+ Returns:
56+ Warning message string
57+ """
58+ return (
59+ "WARNING: Disk space issue detected. Your disk may be full or nearly full.\n"
60+ "This can cause Claude Code to become unresponsive or crash.\n"
61+ "\n"
62+ "Recommended actions:\n"
63+ " 1. Free up disk space by deleting unnecessary files\n"
64+ " 2. Check available space with: df -h\n"
65+ " 3. Clean up temporary files: sudo rm -rf /tmp/* (use with caution)\n"
66+ " 4. Empty trash/recycle bin\n"
67+ " 5. Consider removing old Docker images: docker system prune"
68+ )
69+
70+
71+def check_available_disk_space(path: str = None, min_bytes: int = 10 * 1024 * 1024) -> Tuple[bool, Optional[str]]:
72+ """Check if there's sufficient disk space available.
73+
74+ Args:
75+ path: Path to check (defaults to home directory)
76+ min_bytes: Minimum required bytes (default: 10MB)
77+
78+ Returns:
79+ Tuple of (has_space, warning_message)
80+ - has_space: True if sufficient space available
81+ - warning_message: Warning string if low on space, None otherwise
82+ """
83+ if path is None:
84+ path = os.path.expanduser("~")
85+
86+ try:
87+ # Get disk usage statistics
88+ stat = os.statvfs(path)
89+ available_bytes = stat.f_frsize * stat.f_bavail
90+
91+ if available_bytes < min_bytes:
92+ available_mb = available_bytes / (1024 * 1024)
93+ required_mb = min_bytes / (1024 * 1024)
94+ return False, (
95+ f"Low disk space warning: Only {available_mb:.1f}MB available "
96+ f"(recommended minimum: {required_mb:.1f}MB)\n"
97+ f"{get_disk_space_warning()}"
98+ )
99+
100+ return True, None
101+
102+ except (OSError, AttributeError):
103+ # os.statvfs not available on all platforms (e.g., Windows)
104+ # Return True and let actual write operations fail if there's no space
105+ return True, None
106+
107+
108+def safe_write_file(path: str, content: str, warn_on_disk_error: bool = True) -> Tuple[bool, Optional[str]]:
109+ """Safely write content to a file with disk space error handling.
110+
111+ Args:
112+ path: Path to write to
113+ content: Content to write
114+ warn_on_disk_error: If True, print warning to stderr on disk space errors
115+
116+ Returns:
117+ Tuple of (success, error_message)
118+ - success: True if write succeeded
119+ - error_message: Error description if failed, None otherwise
120+ """
121+ try:
122+ # Ensure directory exists
123+ dir_path = os.path.dirname(path)
124+ if dir_path:
125+ os.makedirs(dir_path, exist_ok=True)
126+
127+ with open(path, 'w') as f:
128+ f.write(content)
129+
130+ return True, None
131+
132+ except Exception as e:
133+ if is_disk_space_error(e):
134+ error_msg = f"Disk space error writing to {path}: {e}\n{get_disk_space_warning()}"
135+ if warn_on_disk_error:
136+ print(error_msg, file=sys.stderr)
137+ return False, error_msg
138+ else:
139+ return False, f"Error writing to {path}: {e}"
140+
141+
142+def safe_append_file(path: str, content: str, warn_on_disk_error: bool = True) -> Tuple[bool, Optional[str]]:
143+ """Safely append content to a file with disk space error handling.
144+
145+ Args:
146+ path: Path to append to
147+ content: Content to append
148+ warn_on_disk_error: If True, print warning to stderr on disk space errors
149+
150+ Returns:
151+ Tuple of (success, error_message)
152+ - success: True if append succeeded
153+ - error_message: Error description if failed, None otherwise
154+ """
155+ try:
156+ # Ensure directory exists
157+ dir_path = os.path.dirname(path)
158+ if dir_path:
159+ os.makedirs(dir_path, exist_ok=True)
160+
161+ with open(path, 'a') as f:
162+ f.write(content)
163+
164+ return True, None
165+
166+ except Exception as e:
167+ if is_disk_space_error(e):
168+ error_msg = f"Disk space error appending to {path}: {e}\n{get_disk_space_warning()}"
169+ if warn_on_disk_error:
170+ print(error_msg, file=sys.stderr)
171+ return False, error_msg
172+ else:
173+ return False, f"Error appending to {path}: {e}"
@@ -10,18 +10,40 @@
1010 import sys
1111 from datetime import datetime
1212
13+# Import disk space utilities
14+try:
15+ from disk_space_utils import (
16+ is_disk_space_error,
17+ get_disk_space_warning,
18+ check_available_disk_space,
19+ safe_write_file,
20+ safe_append_file,
21+ )
22+ DISK_UTILS_AVAILABLE = True
23+except ImportError:
24+ # Fallback if disk_space_utils not available
25+ DISK_UTILS_AVAILABLE = False
26+
1327 # Debug log file
1428 DEBUG_LOG_FILE = "/tmp/security-warnings-log.txt"
1529
30+# Track if we've already warned about disk space in this session
31+_disk_space_warned = False
1632
33+
1734 def debug_log(message):
1835 """Append debug message to log file with timestamp."""
36+ global _disk_space_warned
1937 try:
2038 timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
2139 with open(DEBUG_LOG_FILE, "a") as f:
2240 f.write(f"[{timestamp}] {message}\n")
2341 except Exception as e:
24- # Silently ignore logging errors to avoid disrupting the hook
42+ # Check if this is a disk space error and warn the user
43+ if DISK_UTILS_AVAILABLE and is_disk_space_error(e) and not _disk_space_warned:
44+ _disk_space_warned = True
45+ print(f"[Security Hook] {get_disk_space_warning()}", file=sys.stderr)
46+ # Continue silently to avoid disrupting the hook
2547 pass
2648
2749
@@ -158,26 +180,44 @@
158180
159181 def load_state(session_id):
160182 """Load the state of shown warnings from file."""
183+ global _disk_space_warned
161184 state_file = get_state_file(session_id)
162185 if os.path.exists(state_file):
163186 try:
164187 with open(state_file, "r") as f:
165188 return set(json.load(f))
166- except (json.JSONDecodeError, IOError):
189+ except json.JSONDecodeError:
190+ debug_log(f"JSON decode error reading state file: {state_file}")
167191 return set()
192+ except Exception as e:
193+ # Check for disk-related errors (corrupted filesystem, etc.)
194+ if DISK_UTILS_AVAILABLE and is_disk_space_error(e):
195+ if not _disk_space_warned:
196+ _disk_space_warned = True
197+ print(f"[Security Hook] {get_disk_space_warning()}", file=sys.stderr)
198+ debug_log(f"Error loading state file: {e}")
199+ return set()
168200 return set()
169201
170202
171203 def save_state(session_id, shown_warnings):
172204 """Save the state of shown warnings to file."""
205+ global _disk_space_warned
173206 state_file = get_state_file(session_id)
174207 try:
175208 os.makedirs(os.path.dirname(state_file), exist_ok=True)
176209 with open(state_file, "w") as f:
177210 json.dump(list(shown_warnings), f)
178- except IOError as e:
179- debug_log(f"Failed to save state file: {e}")
180- pass # Fail silently if we can't save state
211+ except Exception as e:
212+ # Check for disk space errors and provide user-friendly warning
213+ if DISK_UTILS_AVAILABLE and is_disk_space_error(e):
214+ if not _disk_space_warned:
215+ _disk_space_warned = True
216+ print(f"[Security Hook] {get_disk_space_warning()}", file=sys.stderr)
217+ debug_log(f"Disk space error saving state file: {e}")
218+ else:
219+ debug_log(f"Failed to save state file: {e}")
220+ # Fail silently to not disrupt operation
181221
182222
183223 def check_patterns(file_path, content):
@@ -216,6 +256,8 @@
216256
217257 def main():
218258 """Main hook function."""
259+ global _disk_space_warned
260+
219261 # Check if security reminders are enabled
220262 security_reminder_enabled = os.environ.get("ENABLE_SECURITY_REMINDER", "1")
221263
@@ -223,6 +265,13 @@
223265 if security_reminder_enabled == "0":
224266 sys.exit(0)
225267
268+ # Check for low disk space and warn user (only once per session)
269+ if DISK_UTILS_AVAILABLE and not _disk_space_warned:
270+ has_space, warning = check_available_disk_space()
271+ if not has_space:
272+ _disk_space_warned = True
273+ print(f"[Security Hook] {warning}", file=sys.stderr)
274+
226275 # Periodically clean up old state files (10% chance per run)
227276 if random.random() < 0.1:
228277 cleanup_old_state_files()