-
-
Notifications
You must be signed in to change notification settings - Fork 295
feat: Add professional WSL support for Windows + Claude Code integration #92
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
- Smart WSL Detection: Uses wsl --list to detect actual installed distributions - Intelligent Username Resolution: Discovers WSL usernames by scanning home directories - Targeted Path Generation: Only creates paths for confirmed WSL installations - Dual Path Support: Tries both \\wsl$ and \\wsl.localhost formats - Modular Architecture: New dedicated wsl_utils.py module with comprehensive testing - Recursive JSONL Search: Finds Claude data in project subdirectories - Graceful Fallbacks: Robust error handling with sensible defaults Fixes Windows users unable to monitor Claude Code running in WSL environments. Handles username mismatches between Windows and WSL usernames.
WalkthroughThis update introduces robust support for Windows Subsystem for Linux (WSL) in the Claude monitor project. It adds WSL path detection utilities, enhances data path resolution to include WSL environments, updates documentation with detailed WSL integration instructions, and provides comprehensive tests for WSL detection and integration logic. Project metadata and changelog are also updated. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant CLI_Main
participant Data_Reader
participant WSL_Utils
User->>CLI_Main: Run monitor (no custom path)
CLI_Main->>Data_Reader: _resolve_claude_data_path()
Data_Reader->>WSL_Utils: get_wsl_claude_paths()
WSL_Utils-->>Data_Reader: List of WSL paths (or empty if not available)
Data_Reader->>Data_Reader: Check for JSONL files in candidate paths
Data_Reader-->>CLI_Main: Resolved data path
CLI_Main-->>User: Monitor runs with detected data path
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (5)
🚧 Files skipped from review as they are similar to previous changes (5)
✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (6)
src/claude_monitor/data/reader.py (2)
9-10: Remove unused importsThe
osandplatformimports are not used in this file.-import os -import platform
50-59: Consider moving the import to module level for better performanceImporting
get_wsl_claude_pathsinside the function adds overhead on each call. Since this function is called from bothload_usage_entriesandload_all_raw_entries, consider importing at the module level with a try/except block.Move the import to the top of the file:
# At module level after other imports try: from claude_monitor.utils.wsl_utils import get_wsl_claude_paths WSL_AVAILABLE = True except ImportError: WSL_AVAILABLE = False get_wsl_claude_paths = NoneThen simplify the function:
- # Add WSL paths if available - try: - from claude_monitor.utils.wsl_utils import get_wsl_claude_paths - wsl_paths = get_wsl_claude_paths() - candidate_paths.extend(wsl_paths) - if wsl_paths: - logger.debug(f"Added {len(wsl_paths)} WSL Claude paths") - except ImportError: - logger.debug("WSL utilities not available") - except Exception as e: - logger.debug(f"Error getting WSL paths: {e}") + # Add WSL paths if available + if WSL_AVAILABLE and get_wsl_claude_paths: + try: + wsl_paths = get_wsl_claude_paths() + candidate_paths.extend(wsl_paths) + if wsl_paths: + logger.debug(f"Added {len(wsl_paths)} WSL Claude paths") + except Exception as e: + logger.debug(f"Error getting WSL paths: {e}")src/claude_monitor/utils/wsl_utils.py (2)
33-39: Consider increasing subprocess timeout for slow systemsThe 5-second timeout might be insufficient on slower systems or when WSL is starting up. Consider making this configurable or increasing to 10-15 seconds.
+WSL_COMMAND_TIMEOUT = 10 # seconds + class WSLDetector:Then use it in the subprocess calls:
- timeout=5 + timeout=WSL_COMMAND_TIMEOUT
182-189: Consider clarifying function behavior in documentationThe function name
is_wsl_available()suggests it only checks WSL availability, but it also requires distributions to be installed. This dual requirement should be clearly documented.def is_wsl_available() -> bool: """Check if WSL is available on this system. Returns: - True if WSL is available and has distributions installed. + True if WSL is available AND has at least one distribution installed. + Returns False if WSL is not available or no distributions are installed. """src/claude_monitor/cli/main.py (2)
83-83: Handle case where paths_to_check has fewer than 5 elementsThe slice
paths_to_check[:5]is safe, but the log message assumes there are more paths when there might not be.- logger.debug(f"Checking {len(paths_to_check)} paths: {paths_to_check[:5]}...") # Show first 5 + if len(paths_to_check) > 5: + logger.debug(f"Checking {len(paths_to_check)} paths: {paths_to_check[:5]}...") + else: + logger.debug(f"Checking {len(paths_to_check)} paths: {paths_to_check}")
67-77: Consider architectural improvements to avoid circular dependenciesImporting
_resolve_claude_data_pathfromclaude_monitor.data.readercreates a potential circular dependency since that module might import from CLI. Also, the underscore prefix suggests this is a private function not meant for external use.Consider one of these approaches:
- Move the path resolution logic to a shared utility module that both CLI and data.reader can import
- Make
_resolve_claude_data_patha public function if it's meant to be used externally- Consolidate all path discovery logic in one place to avoid duplication
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (8)
CHANGELOG.md(1 hunks)README.md(1 hunks)pyproject.toml(2 hunks)src/claude_monitor/cli/main.py(2 hunks)src/claude_monitor/data/reader.py(4 hunks)src/claude_monitor/utils/wsl_utils.py(1 hunks)src/tests/test_wsl_integration.py(1 hunks)src/tests/test_wsl_utils.py(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
src/tests/test_wsl_utils.py (1)
src/claude_monitor/utils/wsl_utils.py (7)
WSLDetector(13-169)get_wsl_claude_paths(172-179)is_wsl_available(23-45)is_wsl_available(182-189)get_distributions(47-81)get_current_user(131-134)get_claude_paths(136-169)
src/claude_monitor/data/reader.py (1)
src/claude_monitor/utils/wsl_utils.py (1)
get_wsl_claude_paths(172-179)
src/claude_monitor/cli/main.py (2)
src/claude_monitor/utils/wsl_utils.py (1)
get_wsl_claude_paths(172-179)src/claude_monitor/data/reader.py (1)
_resolve_claude_data_path(34-89)
🔇 Additional comments (7)
pyproject.toml (2)
9-9: Verify the version number change.The version appears to be set to "3.0.1", but according to the AI summary, this represents a downgrade from "3.0.4". Please confirm this version number is correct for this WSL support release.
37-37: LGTM: Windows classifier appropriately enabled.Uncommenting the Windows operating system classifier correctly reflects the new WSL support functionality being added to the project.
CHANGELOG.md (1)
3-17: Excellent changelog documentation.The changelog entry comprehensively documents the WSL support enhancement with clear categorization and detailed feature descriptions. The structure follows good changelog practices and accurately reflects the scope of changes in this PR.
src/tests/test_wsl_utils.py (1)
1-128: Excellent test coverage for WSL utilities.This comprehensive test suite properly covers all aspects of WSL detection and path resolution:
- ✅ Platform-specific behavior (Windows vs. Linux)
- ✅ Subprocess interaction mocking for WSL commands
- ✅ Environment variable handling for username detection
- ✅ Path generation logic for different WSL formats
- ✅ Edge cases (no distributions, command failures)
- ✅ Proper isolation using mocks
The test structure follows best practices with descriptive test names, clear assertions, and appropriate use of mocking to avoid external dependencies.
src/tests/test_wsl_integration.py (1)
1-59: Well-designed integration tests for WSL functionality.These integration tests effectively verify the interaction between WSL utilities and the Claude data reader:
- ✅ Custom path override behavior
- ✅ WSL path resolution integration
- ✅ Graceful error handling for missing WSL utilities
- ✅ Proper fallback when WSL paths exist but contain no data
The tests use appropriate mocking strategies and focus on integration behavior rather than implementation details.
README.md (1)
1057-1112: Outstanding documentation for WSL support.The new Windows + WSL Setup section provides comprehensive and user-friendly guidance:
- ✅ Clear explanation of automatic detection capabilities
- ✅ Concrete examples with proper placeholder usage
- ✅ Manual configuration options as fallback
- ✅ Detailed troubleshooting steps for common issues
- ✅ Debug logging guidance for path detection issues
The documentation effectively bridges the gap between technical implementation and user experience, making the WSL feature accessible to users of all technical levels.
src/claude_monitor/utils/wsl_utils.py (1)
156-159: Verify UNC path syntax on a Windows hostThe Linux-based
pathlib.Pathcheck only shows there’s no WSL share mounted in this sandbox—it doesn’t confirm whether forward-slash UNC paths are accepted on Windows. Windows UNC paths conventionally use backslashes (\\…), so please verify both formats on an actual Windows machine (e.g. via File Explorer, PowerShell’sTest-Path, or Python’spathlib.WindowsPath):
\\wsl$\{distro}\home\{username}\.claude\projects\\wsl.localhost\{distro}\home\{username}\.claude\projectsIf you need to stick with forward slashes for cross-platform consistency, ensure you test that
//wsl.localhost/...works in all your target Windows environments.
| for line in result.stdout.strip().split('\n'): | ||
| if line.strip(): | ||
| # Remove BOM and clean up distribution name | ||
| distro = line.strip().replace('\x00', '').replace('\ufeff', '') | ||
| if distro: | ||
| distributions.append(distro) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Improve robustness of distribution name parsing
The current string cleaning approach might miss edge cases. Consider using a more robust method to handle Unicode issues.
- for line in result.stdout.strip().split('\n'):
- if line.strip():
- # Remove BOM and clean up distribution name
- distro = line.strip().replace('\x00', '').replace('\ufeff', '')
- if distro:
- distributions.append(distro)
+ import codecs
+ # Decode with BOM handling
+ output = result.stdout
+ if output.startswith(codecs.BOM_UTF8.decode('utf-8')):
+ output = output[1:]
+
+ for line in output.strip().split('\n'):
+ # Clean up distribution name
+ distro = line.strip().replace('\x00', '')
+ # Filter out empty or special entries
+ if distro and not distro.startswith('.'):
+ distributions.append(distro)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| for line in result.stdout.strip().split('\n'): | |
| if line.strip(): | |
| # Remove BOM and clean up distribution name | |
| distro = line.strip().replace('\x00', '').replace('\ufeff', '') | |
| if distro: | |
| distributions.append(distro) | |
| import codecs | |
| # Decode with BOM handling | |
| output = result.stdout | |
| if output.startswith(codecs.BOM_UTF8.decode('utf-8')): | |
| output = output[1:] | |
| for line in output.strip().split('\n'): | |
| # Clean up distribution name | |
| distro = line.strip().replace('\x00', '') | |
| # Filter out empty or special entries | |
| if distro and not distro.startswith('.'): | |
| distributions.append(distro) |
🤖 Prompt for AI Agents
In src/claude_monitor/utils/wsl_utils.py around lines 68 to 73, the current
method of cleaning distribution names by manually replacing BOM and null
characters is not robust against all Unicode edge cases. Replace this approach
with a more reliable Unicode normalization method, such as using Python's
unicodedata.normalize function, to properly handle and clean the distribution
strings before appending them to the list.
|
@hkirste Hi, thanks for your contribution! I've sent you an invite as a contributor. Windows integration is planned: #81. I’ll have time to look into it tomorrow or the day after. |
Hi, it does! Currently Claude Code on Windows is only available through WSL, so for me it wasn't recognizing the Claude Code JSONL.
|
@hkirste Claude Code natively supports Windows now, as long as you run it through a POSIX-compliant shell (e.g., MSYS2 bash or the one bundled with Git for Windows). |
- Bump version from 3.0.1 to 3.0.5 - Fix test_discover_claude_data_paths_with_custom by adding missing mock for Path.rglob - Test now properly mocks JSONL file detection in custom paths
|
Codecov ReportAttention: Patch coverage is
❗ Your organization needs to install the Codecov GitHub app to enable full functionality. Additional details and impacted files@@ Coverage Diff @@
## main #92 +/- ##
==========================================
- Coverage 71.14% 71.13% -0.01%
==========================================
Files 37 38 +1
Lines 2921 3097 +176
Branches 431 465 +34
==========================================
+ Hits 2078 2203 +125
- Misses 737 777 +40
- Partials 106 117 +11 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
pyproject.toml(2 hunks)src/tests/test_cli_main.py(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- pyproject.toml
🧰 Additional context used
🪛 GitHub Actions: Lint
src/tests/test_cli_main.py
[error] 113-113: Ruff I001: Import block is un-sorted or un-formatted. Organize imports.
🔇 Additional comments (2)
src/tests/test_cli_main.py (2)
117-117: LGTM! Mock JSONL files structure is appropriate.The creation of
mock_jsonl_filesas a list ofPathobjects correctly simulates the presence of JSONL files that would be discovered by the recursive search functionality.
121-121: Patch target forrglobis correctThe tests mock
pathlib.Path.rglob, which aligns with howdiscover_claude_data_paths(src/claude_monitor/cli/main.py:93) and the reader functions (src/claude_monitor/data/reader.py:70, 182) invokePath.rglob("*.jsonl"). No changes are needed here.
| def test_discover_claude_data_paths_with_custom(self) -> None: | ||
| """Test discover with custom paths.""" | ||
| from claude_monitor.cli.main import discover_claude_data_paths | ||
| from pathlib import Path |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix redundant import flagged by linter.
The import of Path from pathlib is redundant since Path is already imported on line 3. This creates an import organization issue as flagged by the linter.
Remove the redundant import:
- from pathlib import Path📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| from pathlib import Path |
🤖 Prompt for AI Agents
In src/tests/test_cli_main.py at line 114, remove the redundant import statement
"from pathlib import Path" since Path is already imported at line 3. This will
resolve the linter warning about duplicate imports and improve import
organization.
- Remove unused imports (os, platform, Set) - Fix import ordering and formatting - Remove trailing whitespace from all files - Add missing newlines at end of files - Fix test mock to include Path.rglob for JSONL detection All linting errors resolved, code now passes ruff checks.
Apply automatic code formatting with ruff format to ensure consistent code style. Changes include: - Proper line breaking for long function calls - Consistent string quote formatting - Improved readability of multi-line expressions - Standard import formatting All tests continue to pass after formatting changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🔭 Outside diff range comments (1)
src/claude_monitor/cli/main.py (1)
1-412: Apply formatting fixes from ruffThe
ruff format --check --diffoutput shows thatsrc/claude_monitor/cli/main.pyneeds minor reformatting:
- Add blank lines after imports and before logger assignments
- Wrap long
logger.debug(...)calls onto multiple linesRun:
ruff format --fix src/claude_monitor/cli/main.pyto automatically apply these changes.
♻️ Duplicate comments (1)
src/claude_monitor/cli/main.py (1)
63-64: Remove duplicate importThe
loggingmodule is already imported at the top of the file (line 5).- import logging - logger = logging.getLogger(__name__) + logger = logging.getLogger(__name__)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
src/claude_monitor/cli/main.py(2 hunks)src/claude_monitor/data/reader.py(3 hunks)src/claude_monitor/utils/wsl_utils.py(1 hunks)src/tests/test_cli_main.py(1 hunks)src/tests/test_wsl_integration.py(1 hunks)src/tests/test_wsl_utils.py(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
- src/tests/test_cli_main.py
- src/claude_monitor/data/reader.py
- src/tests/test_wsl_utils.py
- src/claude_monitor/utils/wsl_utils.py
- src/tests/test_wsl_integration.py
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/claude_monitor/cli/main.py (2)
src/claude_monitor/utils/wsl_utils.py (1)
get_wsl_claude_paths(172-179)src/claude_monitor/data/reader.py (1)
_resolve_claude_data_path(32-87)
🪛 GitHub Actions: Lint
src/claude_monitor/cli/main.py
[error] 1-1: Ruff formatting check failed. File would be reformatted.
🔇 Additional comments (2)
src/claude_monitor/cli/main.py (2)
66-78: Excellent smart path resolution integration!The prioritized approach using
_resolve_claude_data_pathwith fallback to legacy discovery is well-implemented. The logging provides good visibility into the path resolution process.
88-105: Robust path validation with JSONL checking.The enhanced path discovery with recursive JSONL file checking and comprehensive error handling is a significant improvement. The fallback logic correctly prioritizes directories with actual Claude data.
| from claude_monitor.utils.wsl_utils import get_wsl_claude_paths | ||
| wsl_paths = get_wsl_claude_paths() | ||
| paths.extend(str(path) for path in wsl_paths) | ||
| except Exception: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Use more specific exception handling.
The broad Exception catch might hide important errors. Consider catching specific exceptions like ImportError or AttributeError that are expected during WSL detection.
- except Exception:
+ except (ImportError, AttributeError) as e:
+ logger.debug(f"WSL detection unavailable: {e}")📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| except Exception: | |
| except (ImportError, AttributeError) as e: | |
| logger.debug(f"WSL detection unavailable: {e}") |
🤖 Prompt for AI Agents
In src/claude_monitor/cli/main.py at line 47, replace the broad except Exception
clause with more specific exception handling. Identify the expected exceptions
during WSL detection, such as ImportError or AttributeError, and catch only
those to avoid hiding other important errors. Update the except block to handle
these specific exceptions accordingly.
Thanks, I didn't know that, still this implementation tries the Windows directory path aswell as the WSL paths. |

Summary
Adds comprehensive Windows Subsystem for Linux (WSL) support for monitoring Claude Code instances running in WSL from Windows.
Problem Solved
Windows users with Claude Code installed in WSL were unable to monitor their token usage because:
C:\Users\{user}\.claude\projects)\\wsl$\{distro}\home\{user}\.claude\projectsSolution
wsl --listcommand to detect actual installed distributions\\wsl$and\\wsl.localhostaccess methodswsl_utils.pymoduleTechnical Details
New Files
src/claude_monitor/utils/wsl_utils.py- WSL detection and path generationsrc/tests/test_wsl_utils.py- Comprehensive WSL utilities testssrc/tests/test_wsl_integration.py- Integration tests with data readerModified Files
src/claude_monitor/data/reader.py- Enhanced path resolution with WSL supportsrc/claude_monitor/cli/main.py- Streamlined WSL integrationREADME.md- Added Windows + WSL setup documentationCHANGELOG.md- Documented changespyproject.toml- Version bump to 3.0.1Testing
Example Usage