fix: support Windows path encoding for projects, sessions, and subagents#50
fix: support Windows path encoding for projects, sessions, and subagents#50linhkk3214 wants to merge 10 commits intoJayantDevkar:mainfrom
Conversation
Claude Code on Windows encodes project paths differently from Unix:
Unix: /Users/me/repo -> -Users-me-repo (leading / becomes -)
Windows: C:\Code\Tools -> C--Code-Tools (colon+backslash become dashes)
Without this fix, the API failed to discover any projects on Windows because
all encoded dir names like "C--Code-Tools" were filtered out by startswith("-")
checks, and encode_path/decode_path produced incorrect paths.
Changes:
- models/project.py: fix encode_path for Windows absolute paths (C:\ -> C--);
fix decode_path to recover C:/... paths from C-- encoded names
- utils.py: add is_encoded_project_dir() helper (handles both - and X-- prefixes);
replace all startswith("-") dir-scan guards with the new helper
- db/indexer.py: use is_encoded_project_dir() for project dir filtering
- routers/{projects,sessions,agent_analytics,history}.py: same guard fix
- services/{desktop_sessions,session_lookup}.py: same guard fix
- services/subagent_types.py: open JSONL with encoding="utf-8", errors="replace"
to prevent UnicodeDecodeError (cp1252 default on Windows) for subagent pages
- models/{history,plugin}.py: same UTF-8 encoding fix for file reads
- config.py: add localhost:5199 to CORS allowed origins
- Introduced `quick_browser_test.py` for quick visual checks of critical pages. - Created `test_browser_ui.py` for detailed UI testing with content verification. - Added `test_subagent_ui.py` for testing subagent session viewing. - Implemented `test_ui_comprehensive.py` for a final comprehensive UI test with actual content verification. - Developed `test_ui_detailed.py` for thorough testing of all screens, including navigation, filters, and accessibility. - Added batch scripts (`start.bat` and `start.sh`) for easy backend and frontend startup. - Created initial `ui_test_results.json` for storing test results. - Enhanced error handling and logging throughout the test scripts.
- Changed chartCanvas from derived.by to direct let binding - Added svelte-ignore directive for non-reactive update - Fixes Svelte compiler warnings about bind:this with derived
JayantDevkar
left a comment
There was a problem hiding this comment.
Code Review — PR #50: Windows Path Encoding Fix
Thanks for the contribution! The core Windows path encoding fix is well-implemented and correct. The encode_path/decode_path logic, is_encoded_project_dir() guard, and UTF-8 file read fixes all solve the right problems cleanly.
However, there are several issues that need addressing before merge. I've categorized them by severity:
CRITICAL (1) — Must fix
1. Default API port changed from 8000 → 9005 — frontend/src/lib/config.ts:21
The fallback API_BASE URL was changed from http://localhost:8000 to http://localhost:9005. This appears to be a local dev config that leaked in. The CLAUDE.md, README, and all project docs reference port 8000. This will break the app for every user who clones and runs without setting PUBLIC_API_URL.
HIGH (3) — Should fix
2. Two test files still use old startswith("-") guard
api/tests/api/test_projects.py:92api/tests/api/test_sessions.py:63
These test helpers iterate project dirs with the old guard. If someone runs tests against Windows fixtures, these would silently skip Windows-encoded directories, masking bugs.
3. No unit tests for Windows encode/decode logic
The PR adds critical new behavior (encode_path for Windows, decode_path for Windows, is_encoded_project_dir for Windows patterns), but there are zero tests for these Windows code paths. Tests should cover:
encode_path("C:\\Code\\Tools")→"C--Code-Tools"decode_path("C--Code-Tools")→"C:/Code/Tools"is_encoded_project_dir("C--Code-Tools")→Trueis_encoded_project_dir("memory")→False
4. Root-level test scripts and artifacts are repo bloat
12 files were added to the repo root that don't belong:
- 6 ad-hoc Python test scripts (
quick_browser_test.py,test_browser_ui.py, etc.) - 3 JSON test result files (
ui_test_results.json, etc.) - 2 start scripts (
start.bat,start.sh) using non-standard ports
These aren't integrated into the project's pytest suite, reference hardcoded localhost URLs, and include committed test output data. Will remove before merge.
MEDIUM (4) — Fix when possible
5. is_encoded_project_dir could false-positive on single-letter dirs — api/utils.py:37
The regex ^[A-Za-z]-- would match a dir literally named a--something. Unlikely in ~/.claude/projects/ but worth a docstring note.
6. Redundant import re inside functions — api/models/project.py:130, api/db/indexer.py:586
re is imported at function scope in decode_path and _detect_project_path. Should be at module top in project.py.
7. _resolve_manifest_dirs exported with underscore prefix — api/models/plugin.py
Used across routers despite being "private". Will drop the underscore since it's public API.
8. Scope creep — PR bundles ~10 unrelated features (image attachments, timezone fixes, plugin manifests, skills UI, CollapsibleGroup rewrite). Not blocking since most came from the rebase, but worth noting.
LOW (4) — Optional
9. config.ts JSDoc still says "localhost:8000" (resolves with CRITICAL fix)
10. api/docs/review/sessions/api.md:180 still has old startswith("-") pattern
11. start.sh uses kill -9 without trying graceful SIGTERM first
12. f-string logging in plugin.py instead of lazy %s formatting
I'll address all of these on the branch. The core Windows fix is solid — these are cleanup items to get it merge-ready. 👍
Review fixes for the Windows path encoding PR:
CRITICAL:
- Revert API_BASE default port from 9005 back to 8000 (contributor's
local config leaked into config.ts)
HIGH:
- Remove 11 root-level test scripts, JSON results, and start scripts
that were committed as debugging artifacts
- Update test_projects.py and test_sessions.py to use
is_encoded_project_dir() instead of startswith("-")
- Add 21 unit tests: TestEncodePathWindows (5), TestDecodePathWindows (5),
TestIsEncodedProjectDir (11), fix wrong assertion in existing test
MEDIUM:
- Move `import re` from function scope to module level in project.py
- Rename _resolve_manifest_dirs → resolve_manifest_dirs (public API,
used across plugin.py, plugins.py, skills.py)
- Add docstring note about false-positive edge case in
is_encoded_project_dir()
All 1359 tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…evkar#48) * feat: extract and display image attachments from user messages Parses base64 image blocks from UserMessage content, surfaces them via image_attachments field, and renders them in the frontend timeline, conversation overview, and expandable prompt components. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(api): use local timezone for dashboard stats and daily trend grouping The dashboard "today"/"yesterday"/"this week" stats and all 28 SQL daily trend queries were using UTC date boundaries instead of the machine's local timezone. On a UTC-8 machine, this caused the homepage to show 1 session instead of 24 for "today". - Add shared `local_timezone()` and `utc_to_local_date()` helpers in utils.py using `datetime.astimezone()` (DST-safe, no stale offsets) - Fix dashboard endpoint to use local calendar date boundaries - Replace all DATE(s.start_time) with timezone-adjusted expressions in db/queries.py (28 occurrences) - Fix Python-side date grouping in plugins router (5 occurrences) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(plugins): detect skills from manifest custom paths Plugins like impeccable store skills in non-default directories (e.g., .claude/skills/ instead of skills/) and declare these paths in their .claude-plugin/plugin.json manifest. Our capability scanner only checked hardcoded default directories, returning 0 skills. - Add read_plugin_manifest() and _resolve_manifest_dirs() helpers to scan both default and manifest-declared custom paths - Update scan_plugin_capabilities(), read_command_contents(), list_plugin_skills(), get_plugin_skill_content() to use them - Update _resolve_skill_info() to check manifest paths when resolving individual skill files - Fix route ordering: move /{plugin_name:path} catch-all to end so /skills and /skills/content sub-routes are reachable - Add "Browse all N skill files" link on plugin detail page Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: replace ambiguous for/break with next() for first user message lookup The for/break pattern had the break at the same indent as the if, making it always fire on first iteration regardless of the condition. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address code review findings across 3 cherry-picked commits - media_type allowlist for image attachments (prevents non-image data URIs) - rename shadowed `date` param to `day` in _local_day_boundaries - guard naive datetimes in utc_to_local_date (assume UTC) - remove unused plugins_base variable in _resolve_manifest_dirs - promote _find_skill_in_version_dir to module level (was needlessly nested) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: sort imports in plugins and skills routers to pass ruff I001 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: apply ruff formatter to message, plugins, and sessions modules Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: add zero-use skills display * feat: persist accordion state and scroll position on skills/commands pages --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Ayush Jhunjhunwala <48875674+the-non-expert@users.noreply.github.com>
… encoding
Review fixes for the Windows path encoding PR:
CRITICAL:
- Revert API_BASE default port from 9005 back to 8000 (contributor's
local config leaked into config.ts)
HIGH:
- Remove 11 root-level test scripts, JSON results, and start scripts
that were committed as debugging artifacts
- Update test_projects.py and test_sessions.py to use
is_encoded_project_dir() instead of startswith("-")
- Add 21 unit tests: TestEncodePathWindows (5), TestDecodePathWindows (5),
TestIsEncodedProjectDir (11), fix wrong assertion in existing test
MEDIUM:
- Move `import re` from function scope to module level in project.py
- Rename _resolve_manifest_dirs → resolve_manifest_dirs (public API,
used across plugin.py, plugins.py, skills.py)
- Add docstring note about false-positive edge case in
is_encoded_project_dir()
All 1359 tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Closes 6 gaps in cross-platform path handling: API — eliminate code duplication: - history.py: replace standalone encode_path() with delegation to canonical Project.encode_path() (was Unix-only, now handles Windows) - indexer.py: replace inline decode/encode in _detect_project_path() and _resolve_project_path() with Project.decode_path()/encode_path() API — cross-platform path recognition: - project.py: add _is_absolute_path() helper that recognizes Windows paths (C:\, D:/) and UNC paths (\\server\share) on any host OS - project.py: normalize backslashes in cwd values from JSONL sessions so Windows paths are stored consistently with forward slashes Frontend — Windows path support: - utils.ts: add Windows C-- pattern to decodeProjectPath() for proper drive letter reconstruction (C--Code-Tools → C:/Code/Tools) - utils.ts: add Windows prefix stripping to getProjectNameFromEncoded() - grouped-projects.ts: normalize backslashes in getDisplayName() and getRelativePath() for cross-platform path comparison Tests: 19 new tests covering Windows paths across all changed functions (API: 127 pass, full suite: 1375 pass; frontend: 186 pass) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…used imports - utils.py: remove duplicate local_timezone() and utc_to_local_date() definitions - routers/history.py: remove unused is_encoded_project_dir import - routers/agent_analytics.py, plugins.py, skills.py: fix import sorting (I001) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
56 new tests (52 run on Mac/Linux, 4 Windows-only): - TestEncodingRoundtrip: parametrized encode/decode for all platforms - TestDirectoryFilterCrossPlatform: is_encoded_project_dir coverage - TestIsAbsolutePathCrossPlatform: _is_absolute_path on all formats - TestWindowsSessionCwdRecovery: mock JSONL with Windows backslash cwd - TestMixedProjectListing: Unix + Windows dirs coexist correctly - TestWindowsNativePathlib: real pathlib validation (Windows CI only) - TestNonWindowsCrossPlatform: verifies why our helpers are needed Fixtures: add temp_windows_project_dir and sample_windows_session_jsonl Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
Claude Code on Windows encodes project paths differently from Unix, causing the app to show no projects, broken sessions, and 500 errors on subagent pages when running on Windows.
Root cause: Claude Code encodes paths by replacing path separators with dashes:
/Users/me/repo→-Users-me-repo(leading-)C:\Code\Tools→C--Code-Tools(drive letter +--)All directory-scan guards used
startswith("-"), so Windows-encoded dirs likeC--Code-Toolswere silently skipped. Additionally, file reads used the Windows defaultcp1252encoding instead of UTF-8, causingUnicodeDecodeErrorcrashes on subagent pages.Changes
Core path encoding (
api/models/project.py)encode_path(): handle Windows absolute paths (C:\...→C--...)decode_path(): recoverC:/...fromC--...encoded names via regexWindows-aware dir filter (
api/utils.py)is_encoded_project_dir(name)helper — returnsTruefor both-Users-me-repo(Unix) andC--Code-Tools(Windows)startswith("-")dir-scan guards across the codebase with the new helperFiles updated with the new guard:
api/db/indexer.pyapi/routers/agent_analytics.py,history.py,projects.py,sessions.pyapi/services/desktop_sessions.py,session_lookup.pyUTF-8 encoding fixes
api/services/subagent_types.py— 2open()callsapi/models/history.py,plugin.py—open()callsAll file reads now use
encoding="utf-8", errors="replace"to preventUnicodeDecodeErroron Windows where Python defaults tocp1252.Misc
api/config.py: addlocalhost:5199/127.0.0.1:5199to CORS allowed originsTest plan
GET /projectsreturns all projects (not empty[])-Users-me-repoformat)