Skip to content

Windows parity: remaining gaps after PR #fix/windows-followup-gaps #748

Description

@jamesbrink

Tracking the Windows-specific gaps surfaced during the post-bring-up sweep that aren't addressed by the companion fix branch (fix/windows-followup-gaps — workspace setup/archive shell, subprocess-tree cleanup, CLI pid_alive probe). Each item below was verified against the code, with the call site, severity, and a concrete fix direction.

1. Claude Code credentials stored plaintext on Windows (medium)

File: src/plugin.rs:1276-1369 (read_secure_storage_object / write_secure_storage_object)

macOS uses Keychain via the security CLI. The cfg(not(target_os = \"macos\")) arm writes ~/.claude/.credentials.json and chmods it 0o600 — but the chmod is itself #[cfg(unix)] (line 1359), so on Windows the file inherits whatever ACL %USERPROFILE%\.claude\ has and there is no DPAPI / Credential Manager path.

Why this wasn't fixed in the branch: this file is Claude Code's own credential store; Claudette is interop'ing with it for plugin secrets (pluginSecrets). Encrypting via DPAPI would break the upstream claude CLI unless Claude Code itself uses DPAPI on Windows. We need to confirm Claude Code's Windows storage format before adding encryption — encrypting unilaterally would force every Windows Claudette user to re-auth and would break Claude Code reading the file.

Fix direction:

  1. Verify what Claude Code on Windows does with .credentials.json (encrypted? plaintext? Credential Manager?).
  2. If Claude Code already uses DPAPI: mirror it.
  3. If Claude Code uses plaintext: add a Claudette-side wrapper (e.g., wrap the JSON in a versioned envelope so older Claude Code reads still work, then layer DPAPI). Risk-tolerant variant only.
  4. Alternative: at minimum, set a tight ACL on the parent dir (icacls %USERPROFILE%\.claude /inheritance:r /grant:r %USERNAME%:F) on first run.

2. Sidebar "command running" indicator absent on Windows (low / known limitation)

File: src-tauri/src/pty_tracker.rs:15-17

The whole module is #[cfg(unix)] and documents the limitation in its header comment. Polls tcgetpgrp(2) on the master PTY for the foreground PGID; Windows has no analog tied to the controlling terminal. All call sites are gated, so the sidebar's command indicator silently never lights up on Windows — Windows users get no visual signal that a long-running command is busy in a tab.

Fix direction: candidates in order of feasibility:

  • Document the gap in the docs site so users know it's intentional.
  • Stub event so the UI shows "something running" when the PTY has any non-shell child (poll the conhost the PTY drives via GetConsoleProcessList).
  • Full equivalence via process-enumeration polling — likely flaky and CPU-heavy.

3. Lua env-provider plugins emit hard errors instead of "unavailable" on Windows (low)

Files:

  • plugins/env-direnv/init.lua:33,44-45host.exec(\"direnv\", …)
  • plugins/env-mise/init.lua:35,44-45host.exec(\"mise\", …)
  • plugins/env-nix-devshell/init.lua:49,51host.exec(\"nix\", …)

These tools are rarely on Windows. The plugin runner currently surfaces \"direnv export failed\" / \"mise env failed\" rather than "not available on this platform." The Lua VM has no host.os() registration so plugins can't short-circuit.

Fix direction: register host.os() (returns \"macos\" / \"linux\" / \"windows\") in src/plugin_runtime/host_api.rs, then add if host.os() == \"windows\" then return { available = false } end near the top of each affected plugin.

4. Several tests gated off Windows without a paired path (low)

Files:

  • src/agent/process.rs:344#[cfg(all(test, unix))] whole-module gate; comment: "tests shell out to POSIX-only utilities … so the entire module is gated." No Windows equivalents using taskkill were added.
  • src/env_provider/mod.rs:729apply_sets_and_unsets_vars_on_command body wrapped in #[cfg(unix)]; comment: "Skipped on Windows — sh isn't present."
  • src/plugin_runtime/host_api.rs:842 — symlink-escape test Unix-only.
  • src/file_watcher.rs:601 — symlink-shift test Unix-only.

PR CI doesn't run Windows tests today (CLAUDE.md notes Windows is built only by nightly/release), so the absence isn't visible — but it means agent termination + env apply have zero automated regression coverage on Windows. Note: with the workspace-script fix landed on the companion branch, the env_provider test's "sh isn't present" justification is no longer fully accurate; a cmd.exe-based Windows variant is now writable.

Fix direction: write Windows-equivalents (write a small Rust binary instead of sh -c for the env test; junctions/hardlinks for the file-watcher test; taskkill probes for agent process tests).

5. detect_user_shell() returns /bin/sh on Windows (low / latent)

File: src-tauri/src/commands/shell.rs:31-32

Currently dead-ish — the frontend doesn't call detect_shell (no hits for detect_shell / detectShell in src/ui/), and PTY startup uses CommandBuilder::new_default_prog() which honors COMSPEC correctly. But the function is exported as a Tauri command and any future caller will get a path that doesn't exist on disk.

Fix direction: add a #[cfg(windows)] branch returning std::env::var(\"COMSPEC\").unwrap_or(\"cmd.exe\") with ShellType::Unknown.


What was fixed in the companion branch

For reference, the three high/medium gaps that have been addressed land on fix/windows-followup-gaps:

  1. Setup/archive scripts on Windows — cmd.exe /S /C instead of unconditional sh -c.
  2. Subprocess subtree cleanup on Windows — taskkill /T graceful → OpenProcess poll → taskkill /T /F force, wired into both RunEvent::Exit and close_pty.
  3. CLI pid_alive() probe on Windows — real OpenProcess + GetExitCodeProcess instead of the unconditional true stub.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions