From c4af551375e572896733a68c3e2c6eaf50ce8158 Mon Sep 17 00:00:00 2001 From: Hikari Date: Mon, 13 Apr 2026 15:56:53 -0700 Subject: [PATCH] fix: read/write global CLAUDE.md via WSL on Windows (#264) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 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: https://git.nhcarrigan.com/nhcarrigan/hikari-desktop/pulls/264 Co-authored-by: Hikari Co-committed-by: Hikari --- src-tauri/src/commands.rs | 98 ++++++++++++++++++++++++++++++++------- 1 file changed, 81 insertions(+), 17 deletions(-) diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index bbc1ef0..c1cdbba 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -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 { + 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 { - 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)]