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
| 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}" |