Skip to content

feat: add native Windows support#74

Merged
wpfleger96 merged 7 commits into
mainfrom
worktree-wpfleger-windows-support
Jun 3, 2026
Merged

feat: add native Windows support#74
wpfleger96 merged 7 commits into
mainfrom
worktree-wpfleger-windows-support

Conversation

@wpfleger96
Copy link
Copy Markdown
Owner

@wpfleger96 wpfleger96 commented Jun 2, 2026

Adds native Windows support for Claude Code, Goose, Gemini CLI, and Codex CLI. Amp is WSL-only and remains excluded on Windows. All lifecycle operations (install, status, diff, uninstall, completions) now work on native Windows.

Stack: this PR → #78

Platform detection

  • platform.py exports a 4-value Platform enum (MACOS, LINUX, WINDOWS, WSL) with detect_platform() (@lru_cache), is_platform(), and is_unix_like property — single monkeypatch target for tests
  • WSL detected via platform.uname().release (contains "microsoft") with WSL_DISTRO_NAME env var as secondary signal
  • All platform-specific path helpers (get_goose_config_dir, get_statusline_config_dir, get_uv_tools_dir, etc.) use is_platform(Platform.WINDOWS) internally
  • get_appdata_dir() falls back to Path.home() / "AppData" / "Roaming" when APPDATA is unset (Docker, CI)

Shell detection and completions

  • shellingham adopted for shell detection on all platforms — inspects the process tree via parent PID instead of PSModulePath env var
  • PowerShell profile path queried from pwsh -NoProfile -Command "Write-Output $PROFILE" instead of hardcoded path — OneDrive Folder Backup silently redirects ~/Documents on modern Windows
  • Custom Register-ArgumentCompleter -Native PowerShell completion script using Click's bash_complete protocol with correct COMP_CWORD cursor-position logic

Gemini copy-mode

  • Gemini CLI destroys symlinks when rewriting settings.json — file-copy mode via copy_mode_targets on ConfigTarget
  • create_file_copy includes backup and confirmation when target has user modifications
  • Copy-mode awareness wired into global pending-change and first-run checks
  • Preserved fields read from live file before cache rebuild so Gemini's edits survive re-installs

Windows symlink handling

  • PermissionError surfaces Developer Mode enable guide; target_is_directory passed to symlink_to() (required on Windows)
  • _is_specialized_path uses .as_posix() instead of str() for backslash-safe substring matching
  • Amp excluded from both targets and skills directories on Windows

CI

  • Matrix extended to windows-latest

Cross-platform test suite fixes

  • Autouse _patch_home_from_env fixture makes Path.home() follow HOME env var via lazy closure — isolates tests from the real user home on Windows CI
  • mock_home fixture sets USERPROFILE and APPDATA alongside HOME — Windows code paths that read these env vars resolve to the temp dir
  • PYTHONUTF8=1 set in autouse fixture — prevents cp1252 UnicodeDecodeError when Click's CliRunner captures Rich output containing emoji
  • Path assertions use .as_posix() throughout — str(WindowsPath) produces backslashes that break forward-slash string comparisons
  • UV_TOOL_DIR cleared/overridden in bootstrap tests — Windows CI runners set this env var, overriding the default XDG-based path structure
  • repo_score() in session-search normalizes session paths via .as_posix() — session data contains POSIX paths from macOS/Linux hosts

Related: shell-configs#64, shell-configs#65

wpfleger96 and others added 5 commits June 3, 2026 17:14
Claude Code, Goose, Gemini CLI, and Codex CLI all run natively on Windows.
Amp is WSL-only so it remains Linux-only. This change adds the plumbing to
make all four agents work on native Windows without requiring WSL.

Key design decisions:
- Centralize all platform branching in a new `platform.py` module (single
  monkeypatch target for tests; no scattered sys.platform checks).
- Goose and Statusline use different config dirs on Windows (%APPDATA%/Block
  and %APPDATA%/claude-statusline respectively); helpers return tilde paths
  on Unix for backward compatibility.
- Gemini CLI actively destroys symlinks when rewriting settings.json (GitHub
  issue #10960, closed "not planned"). Windows Gemini gets file copies instead
  of symlinks via a new copy_mode_targets mechanism; preserved fields are read
  from the live target before each cache rebuild to survive Gemini rewrites.
- Windows symlinks require Developer Mode; PermissionError surfaces a
  step-by-step enable guide instead of a generic message.
- PowerShell completions use Register-ArgumentCompleter with bash_source mode,
  setting COMP_WORDS/COMP_CWORD so Click returns contextual completions.
- uv path resolution respects UV_TOOL_DIR override on all platforms.
- CI matrix extended to windows-latest.
Replace is_windows() boolean with Platform enum (MACOS, LINUX, WINDOWS,
WSL) and detect_platform() using platform.uname().release for WSL
detection. Adopt shellingham for shell detection on all platforms --
PSModulePath was inherited by every Windows process, falsely identifying
all shells as PowerShell. Query PowerShell for its profile path via
subprocess instead of hardcoding ~/Documents (OneDrive silently
redirects it). Drop click-pwsh (broken under uv).
Fix Python 3 SyntaxError from bare-comma except clauses. Rewrite
PowerShell completion to use Click's bash_complete mode with correct
COMP_CWORD cursor-position logic. Add backup and confirmation to
create_file_copy when target has user modifications. Wire copy-mode
awareness into global pending-change and first-run checks. Add APPDATA
fallback for stripped Windows environments, filter Amp skills on
Windows, and add shellingham upper-bound pin.
On Windows, Path.home() reads USERPROFILE (not HOME), and str(Path)
uses backslashes. Tests that only set HOME caused 75 failures on Windows
CI because Path.home() resolved to the real C:\Users\runneradmin.

- Add autouse _patch_home_from_env fixture in conftest.py that makes
  Path.home() follow the HOME env var at call time via a lazy closure,
  so all tests that set HOME get correct isolation without any per-test
  changes
- Update mock_home fixture to also set APPDATA for Windows parity
- Fix test_agents.py: use Path.as_posix() when comparing symlink targets
  so ~\.claude\CLAUDE.md comparisons work cross-platform
- Fix test_bootstrap_installer.py: use UV_TOOL_DIR override instead of
  XDG_DATA_HOME in tool-source tests (UV_TOOL_DIR is the canonical env
  var and takes precedence on all platforms); use as_posix() for path
  assertions; clear UV_TOOL_DIR in tests that check default path structure
- Fix test_bootstrap_updater.py: same UV_TOOL_DIR approach for pyvenv.cfg
  tests
- Fix test_session_search_core.py: use Path.as_posix() when comparing
  session_to_json path output; fix matches_term path test to not rely
  on forward-slash path separators in str(path)
- Fix test_session_search_readers.py: also set USERPROFILE in _patch_home
  helper (belt-and-suspenders alongside the autouse fixture)
Four root causes: (1) Click CliRunner uses cp1252 on Windows, causing
UnicodeDecodeError for skill markdown with emoji — fixed via PYTHONUTF8=1
in autouse fixture. (2) Path assertions using forward slashes fail when
Path() stringifies with backslashes — normalized with .replace/as_posix().
(3) repo_score() converts POSIX session paths to backslash form, breaking
all string comparisons — fixed with .as_posix() in production code.
@wpfleger96 wpfleger96 force-pushed the worktree-wpfleger-windows-support branch from a8e6f56 to 34f44e5 Compare June 3, 2026 21:14
Path.read_text() without encoding uses the system default (cp1252 on
Windows). SKILL.md files contain UTF-8 emoji characters that can't be
decoded by cp1252, causing UnicodeDecodeError in all skill CLI commands
on Windows.
Cross-platform testing belongs in the E2E workflow (e2e.yml) which runs
on all 3 OSes. The matrix in ci.yml caused the job name to change from
bare "checks" to "checks (ubuntu-latest)" / "checks (windows-latest)",
which no longer matched the ruleset's required status check context.
@wpfleger96 wpfleger96 merged commit fb08b2e into main Jun 3, 2026
6 checks passed
@wpfleger96 wpfleger96 deleted the worktree-wpfleger-windows-support branch June 3, 2026 22:48
wpfleger96 added a commit that referenced this pull request Jun 3, 2026
…-managed-component-cleanup

* origin/main:
  feat(testing): add cross-platform E2E test suite (#78)
  feat: add native Windows support (#74)
  chore(main): release 0.59.0 (#75)
  docs(agents): add infrastructure awareness, quality gate, git-pull rule, PR cross-referencing; compress 32% (#77)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant