fix: read/write global CLAUDE.md via WSL on Windows (#264)
Security Scan and Upload / Security & DefectDojo Upload (push) Successful in 1m0s
CI / Lint & Test (push) Successful in 16m37s
CI / Build Linux (push) Has been cancelled
CI / Build Windows (cross-compile) (push) Has been cancelled

## Summary

- `get_global_claude_md` and `save_global_claude_md` were using `dirs::home_dir()` which resolves to the Windows home directory (`C:\Users\accou`) on Windows builds
- The actual `~/.claude/CLAUDE.md` lives in the WSL home directory, so the editor was always showing an empty text box and would have written to the wrong location on save
- Added `get_global_claude_md_via_wsl()` and `save_global_claude_md_via_wsl()` helpers that shell out to WSL (matching the existing pattern used by `list_skills` and `list_memory_files`)
- Both Tauri commands now branch on `cfg!(target_os = "windows")` to use the appropriate path

## Test plan

- [ ] Open Settings sidebar on a Windows build — Global Instructions textarea should load the contents of `~/.claude/CLAUDE.md` from WSL
- [ ] Edit the content and click Save — verify the WSL file is updated
- [ ] Verify Linux/macOS builds are unaffected (native filesystem path unchanged)

 This issue was created with help from Hikari~ 🌸

Reviewed-on: #264
Co-authored-by: Hikari <hikari@nhcarrigan.com>
Co-committed-by: Hikari <hikari@nhcarrigan.com>
This commit was merged in pull request #264.
This commit is contained in:
2026-04-13 15:56:53 -07:00
committed by Naomi Carrigan
parent b88f25a61b
commit c4af551375
+81 -17
View File
@@ -2618,37 +2618,101 @@ pub async fn open_binary_file(app: AppHandle, path: String) -> Result<(), String
}
}
/// Read `~/.claude/CLAUDE.md` via WSL (for Windows).
/// Returns an empty string if the file does not exist.
#[cfg(target_os = "windows")]
async fn get_global_claude_md_via_wsl() -> Result<String, String> {
use std::process::Command;
let output = Command::new("wsl")
.hide_window()
.args(["-e", "bash", "-l", "-c", "cat ~/.claude/CLAUDE.md 2>/dev/null || true"])
.output()
.map_err(|e| format!("Failed to execute WSL command: {}", e))?;
Ok(String::from_utf8_lossy(&output.stdout).to_string())
}
/// Write content to `~/.claude/CLAUDE.md` via WSL (for Windows).
/// Creates the file (and `~/.claude/` directory) if they do not exist.
#[cfg(target_os = "windows")]
async fn save_global_claude_md_via_wsl(content: String) -> Result<(), String> {
use std::io::Write;
use std::process::{Command, Stdio};
let mut child = Command::new("wsl")
.hide_window()
.args([
"-e",
"bash",
"-l",
"-c",
"mkdir -p ~/.claude && cat > ~/.claude/CLAUDE.md",
])
.stdin(Stdio::piped())
.spawn()
.map_err(|e| format!("Failed to execute WSL command: {}", e))?;
if let Some(stdin) = child.stdin.as_mut() {
stdin
.write_all(content.as_bytes())
.map_err(|e| format!("Failed to write content to WSL stdin: {}", e))?;
}
let status = child
.wait()
.map_err(|e| format!("Failed to wait for WSL command: {}", e))?;
if !status.success() {
return Err("Failed to save CLAUDE.md via WSL".to_string());
}
Ok(())
}
/// Read the contents of `~/.claude/CLAUDE.md`.
/// Returns an empty string if the file does not exist.
#[tauri::command]
pub async fn get_global_claude_md() -> Result<String, String> {
let path = dirs::home_dir()
.ok_or_else(|| "Could not determine home directory".to_string())?
.join(".claude")
.join("CLAUDE.md");
#[cfg(target_os = "windows")]
return get_global_claude_md_via_wsl().await;
if !path.exists() {
return Ok(String::new());
#[cfg(not(target_os = "windows"))]
{
let path = dirs::home_dir()
.ok_or_else(|| "Could not determine home directory".to_string())?
.join(".claude")
.join("CLAUDE.md");
if !path.exists() {
return Ok(String::new());
}
std::fs::read_to_string(&path).map_err(|e| format!("Failed to read CLAUDE.md: {}", e))
}
std::fs::read_to_string(&path).map_err(|e| format!("Failed to read CLAUDE.md: {}", e))
}
/// Write content to `~/.claude/CLAUDE.md`.
/// Creates the file (and `~/.claude/` directory) if they do not exist.
#[tauri::command]
pub async fn save_global_claude_md(content: String) -> Result<(), String> {
let claude_dir = dirs::home_dir()
.ok_or_else(|| "Could not determine home directory".to_string())?
.join(".claude");
#[cfg(target_os = "windows")]
return save_global_claude_md_via_wsl(content).await;
if !claude_dir.exists() {
std::fs::create_dir_all(&claude_dir)
.map_err(|e| format!("Failed to create ~/.claude directory: {}", e))?;
#[cfg(not(target_os = "windows"))]
{
let claude_dir = dirs::home_dir()
.ok_or_else(|| "Could not determine home directory".to_string())?
.join(".claude");
if !claude_dir.exists() {
std::fs::create_dir_all(&claude_dir)
.map_err(|e| format!("Failed to create ~/.claude directory: {}", e))?;
}
let path = claude_dir.join("CLAUDE.md");
std::fs::write(&path, content).map_err(|e| format!("Failed to write CLAUDE.md: {}", e))
}
let path = claude_dir.join("CLAUDE.md");
std::fs::write(&path, content).map_err(|e| format!("Failed to write CLAUDE.md: {}", e))
}
#[cfg(test)]