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:
- Verify what Claude Code on Windows does with
.credentials.json (encrypted? plaintext? Credential Manager?).
- If Claude Code already uses DPAPI: mirror it.
- 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.
- 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-45 — host.exec(\"direnv\", …)
plugins/env-mise/init.lua:35,44-45 — host.exec(\"mise\", …)
plugins/env-nix-devshell/init.lua:49,51 — host.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:729 — apply_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:
- Setup/archive scripts on Windows —
cmd.exe /S /C instead of unconditional sh -c.
- Subprocess subtree cleanup on Windows —
taskkill /T graceful → OpenProcess poll → taskkill /T /F force, wired into both RunEvent::Exit and close_pty.
- CLI
pid_alive() probe on Windows — real OpenProcess + GetExitCodeProcess instead of the unconditional true stub.
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, CLIpid_aliveprobe). 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
securityCLI. Thecfg(not(target_os = \"macos\"))arm writes~/.claude/.credentials.jsonand chmods it0o600— 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 upstreamclaudeCLI 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:
.credentials.json(encrypted? plaintext? Credential Manager?).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-17The whole module is
#[cfg(unix)]and documents the limitation in its header comment. Pollstcgetpgrp(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:
GetConsoleProcessList).3. Lua env-provider plugins emit hard errors instead of "unavailable" on Windows (low)
Files:
plugins/env-direnv/init.lua:33,44-45—host.exec(\"direnv\", …)plugins/env-mise/init.lua:35,44-45—host.exec(\"mise\", …)plugins/env-nix-devshell/init.lua:49,51—host.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 nohost.os()registration so plugins can't short-circuit.Fix direction: register
host.os()(returns\"macos\"/\"linux\"/\"windows\") insrc/plugin_runtime/host_api.rs, then addif host.os() == \"windows\" then return { available = false } endnear 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 usingtaskkillwere added.src/env_provider/mod.rs:729—apply_sets_and_unsets_vars_on_commandbody wrapped in#[cfg(unix)]; comment: "Skipped on Windows —shisn'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_providertest's "shisn't present" justification is no longer fully accurate; acmd.exe-based Windows variant is now writable.Fix direction: write Windows-equivalents (write a small Rust binary instead of
sh -cfor the env test; junctions/hardlinks for the file-watcher test;taskkillprobes for agent process tests).5.
detect_user_shell()returns/bin/shon Windows (low / latent)File:
src-tauri/src/commands/shell.rs:31-32Currently dead-ish — the frontend doesn't call
detect_shell(no hits fordetect_shell/detectShellinsrc/ui/), and PTY startup usesCommandBuilder::new_default_prog()which honorsCOMSPECcorrectly. 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 returningstd::env::var(\"COMSPEC\").unwrap_or(\"cmd.exe\")withShellType::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:cmd.exe /S /Cinstead of unconditionalsh -c.taskkill /Tgraceful →OpenProcesspoll →taskkill /T /Fforce, wired into bothRunEvent::Exitandclose_pty.pid_alive()probe on Windows — realOpenProcess + GetExitCodeProcessinstead of the unconditionaltruestub.