From 8995316844f7ad50552fbae67fbd35119262796f Mon Sep 17 00:00:00 2001 From: Bjorn de Jong Date: Mon, 27 Jul 2026 19:56:34 +0200 Subject: [PATCH] fix(desktop): use forward slashes for git credential.helper on Windows (#3023) ## Problem On Windows, Projects **Remote** view shows an empty file tree and Sync/Clone fails with a mangled credential-helper path, for example: ``` C:\Users\\AppData\Local\Buzz\git-credential-nostr.exe get: line 1: C:UsersAppDataLocalBuzzgit-credential-nostr.exe: command not found fatal: could not read Username for 'https:///git/...': terminal prompts disabled ``` Buzz injects an absolute path into `credential.helper` via `Path::display()`. On Windows that yields backslashes. Git for Windows runs credential helpers through MinGW bash, which treats `\` as escapes and destroys the path, so NIP-98 auth never runs and the blobless temp clone behind Remote view fails. macOS/Linux are unaffected (paths already use `/`). This is unrelated to shipping a stub helper - the bundled `git-credential-nostr.exe` is a real binary. User `~/.gitconfig` workarounds also cannot help here because Projects git sets `GIT_CONFIG_GLOBAL=/dev/null` and injects its own helper. Closes #3025 ## Fix Normalize the helper path to forward slashes before writing `GIT_CONFIG_VALUE_*`: - `desktop/src-tauri/src/commands/project_git_exec.rs` (Projects Remote / Sync) - `desktop/src-tauri/src/managed_agents/runtime.rs` (agent spawn git auth) Forward slashes are accepted by Git on every platform; on macOS/Linux the replace is a no-op. No `cfg(windows)`, packaging, or libgit2 changes. ## How to reproduce (before) 1. Install Buzz on Windows with Git for Windows 2. Connect to a relay that has a repository with at least one pushed branch 3. Open **Projects** -> select the repo -> **Remote** 4. Observe empty tree; Sync/Clone shows the mangled-path / `command not found` error above ## Test plan - [x] Unit: `cargo test --manifest-path desktop/src-tauri/Cargo.toml credential_helper_config_value` (formatter covered on all platforms; no-op for Unix-style paths) - [x] Local Windows NSIS build + install of this branch - [x] Projects -> Remote / Sync against a Buzz relay repo succeeds on Windows after the fix --------- Signed-off-by: Bjorn de Jong Signed-off-by: Will Pfleger Co-authored-by: Bjorn de Jong Co-authored-by: Cursor Co-authored-by: Will Pfleger --- desktop/scripts/check-file-sizes.mjs | 6 +++-- .../src/commands/project_git_exec.rs | 27 ++++++++++++++++--- .../src-tauri/src/managed_agents/runtime.rs | 3 ++- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/desktop/scripts/check-file-sizes.mjs b/desktop/scripts/check-file-sizes.mjs index e64288dc6..3be318ca7 100644 --- a/desktop/scripts/check-file-sizes.mjs +++ b/desktop/scripts/check-file-sizes.mjs @@ -191,9 +191,11 @@ const overrides = new Map([ // runtime.rs re-entered the list after the #1968 merge: main's // definition-authoritative resolver comments grew it to 982, and the BYOH // typed harness-descriptor resolution in spawn_agent_child landed on top at - // 1020. This PR's session-title env write in spawn_agent_child adds 12. + // 1020. The session-title env write in spawn_agent_child adds 12. // Queued to shrink with the next runtime split pass (#2974 follow-up). - ["src-tauri/src/managed_agents/runtime.rs", 1032], + // +1: #3023 credential-helper slash normalization (MinGW bash treats + // backslashes as escapes). + ["src-tauri/src/managed_agents/runtime.rs", 1033], // applyWorkspace reposDir parameter plus the validateReposDir binding, // threaded through Tauri invokes for configurable repos_dir, plus the // harness-persona-sync `harnessOverride` create-input bit — load-bearing diff --git a/desktop/src-tauri/src/commands/project_git_exec.rs b/desktop/src-tauri/src/commands/project_git_exec.rs index 186c1a1cf..e4a8ad7b4 100644 --- a/desktop/src-tauri/src/commands/project_git_exec.rs +++ b/desktop/src-tauri/src/commands/project_git_exec.rs @@ -173,12 +173,23 @@ fn configure_git_auth(command: &mut Command, auth: &GitAuthConfig, needs_credent return apply_git_config(command, &entries); }; command.env("NOSTR_PRIVATE_KEY", &auth.nsec); - entries.push(("credential.helper", cred_helper.display().to_string())); + entries.push(( + "credential.helper", + credential_helper_config_value(cred_helper), + )); entries.push(("credential.useHttpPath", "true".to_string())); } apply_git_config(command, &entries); } +/// Format a path for git `credential.helper`. +/// +/// Git for Windows invokes helpers via MinGW bash, which treats `\` as +/// escapes. Forward slashes work on every platform git supports. +fn credential_helper_config_value(path: &std::path::Path) -> String { + path.to_string_lossy().replace('\\', "/") +} + fn apply_git_config(command: &mut Command, entries: &[(&str, String)]) { command.env("GIT_CONFIG_COUNT", entries.len().to_string()); for (index, (key, value)) in entries.iter().enumerate() { @@ -316,10 +327,20 @@ fn validate_clone_url_against_relay(clone_url: &str, relay_base: &str) -> Result #[cfg(test)] mod tests { use super::{ - clean_branch, clean_target_ref, git_needs_credentials, git_subcommand, validate_clone_url, - validate_clone_url_against_relay, + clean_branch, clean_target_ref, credential_helper_config_value, git_needs_credentials, + git_subcommand, validate_clone_url, validate_clone_url_against_relay, }; + #[test] + fn credential_helper_config_value_uses_forward_slashes() { + let path = + std::path::PathBuf::from(r"C:\Users\x\AppData\Local\Buzz\git-credential-nostr.exe"); + assert_eq!( + credential_helper_config_value(&path), + "C:/Users/x/AppData/Local/Buzz/git-credential-nostr.exe", + ); + } + #[test] fn git_subcommand_skips_global_config_options() { assert_eq!( diff --git a/desktop/src-tauri/src/managed_agents/runtime.rs b/desktop/src-tauri/src/managed_agents/runtime.rs index 3658f48a9..f3b4cb67f 100644 --- a/desktop/src-tauri/src/managed_agents/runtime.rs +++ b/desktop/src-tauri/src/managed_agents/runtime.rs @@ -835,7 +835,8 @@ pub fn spawn_agent_child( "GIT_CONFIG_KEY_0", format!("credential.{relay_http_url}/git.helper"), ); - command.env("GIT_CONFIG_VALUE_0", cred_helper.display().to_string()); + let helper = cred_helper.to_string_lossy().replace('\\', "/"); + command.env("GIT_CONFIG_VALUE_0", helper); command.env( "GIT_CONFIG_KEY_1", format!("credential.{relay_http_url}/git.useHttpPath"),