diff --git a/desktop/src-tauri/Cargo.lock b/desktop/src-tauri/Cargo.lock
index 28461d551a..f759beb7b5 100644
--- a/desktop/src-tauri/Cargo.lock
+++ b/desktop/src-tauri/Cargo.lock
@@ -5193,6 +5193,7 @@ dependencies = [
  "sprout-core",
  "sprout-persona",
  "sprout-sdk",
+ "strip-ansi-escapes",
  "tar",
  "tauri",
  "tauri-build",
@@ -5300,6 +5301,15 @@ dependencies = [
  "quote",
 ]
 
+[[package]]
+name = "strip-ansi-escapes"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025"
+dependencies = [
+ "vte",
+]
+
 [[package]]
 name = "strsim"
 version = "0.11.1"
@@ -6793,6 +6803,15 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "vte"
+version = "0.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077"
+dependencies = [
+ "memchr",
+]
+
 [[package]]
 name = "walkdir"
 version = "2.5.0"
diff --git a/desktop/src-tauri/Cargo.toml b/desktop/src-tauri/Cargo.toml
index 855e754625..695c4f18fd 100644
--- a/desktop/src-tauri/Cargo.toml
+++ b/desktop/src-tauri/Cargo.toml
@@ -72,5 +72,6 @@ earshot = "1.0"
 rubato = "3.0"
 audioadapter-buffers = "3.0"
 tempfile = "3"
+strip-ansi-escapes = "0.2"
 
 [dev-dependencies]
diff --git a/desktop/src-tauri/src/commands/agent_discovery.rs b/desktop/src-tauri/src/commands/agent_discovery.rs
index 7c27d73bc7..efc4ed308b 100644
--- a/desktop/src-tauri/src/commands/agent_discovery.rs
+++ b/desktop/src-tauri/src/commands/agent_discovery.rs
@@ -1,5 +1,5 @@
 use std::io::Read;
-use tauri::{AppHandle, State};
+use tauri::State;
 
 use crate::{
     app_state::AppState,
@@ -64,7 +64,7 @@ fn install_acp_runtime_blocking(provider_id: &str) -> Result<InstallRuntimeResul
 
     // Phase 1: Install CLI if missing and commands are available.
     if let Some(cli) = provider.underlying_cli {
-        if crate::managed_agents::resolve_command(cli, None).is_none() {
+        if crate::managed_agents::resolve_command(cli).is_none() {
             for cmd in provider.cli_install_commands {
                 let result = run_install_command("cli", cmd);
                 let success = result.success;
@@ -83,7 +83,7 @@ fn install_acp_runtime_blocking(provider_id: &str) -> Result<InstallRuntimeResul
     let adapter_found = provider
         .commands
         .iter()
-        .any(|cmd| crate::managed_agents::resolve_command(cmd, None).is_some());
+        .any(|cmd| crate::managed_agents::resolve_command(cmd).is_some());
     if !adapter_found {
         for cmd in provider.adapter_install_commands {
             let result = run_install_command("adapter", cmd);
@@ -286,7 +286,6 @@ fn truncate_output(s: String) -> String {
 #[tauri::command]
 pub fn discover_managed_agent_prereqs(
     input: DiscoverManagedAgentPrereqsRequest,
-    app: AppHandle,
 ) -> ManagedAgentPrereqsInfo {
     let acp_command = input
         .acp_command
@@ -302,8 +301,8 @@ pub fn discover_managed_agent_prereqs(
         .unwrap_or(DEFAULT_MCP_COMMAND);
 
     ManagedAgentPrereqsInfo {
-        acp: command_availability(acp_command, Some(&app)),
-        mcp: command_availability(mcp_command, Some(&app)),
+        acp: command_availability(acp_command),
+        mcp: command_availability(mcp_command),
     }
 }
 
diff --git a/desktop/src-tauri/src/commands/agent_models.rs b/desktop/src-tauri/src/commands/agent_models.rs
index 3d7bc12f20..dc1ab6059c 100644
--- a/desktop/src-tauri/src/commands/agent_models.rs
+++ b/desktop/src-tauri/src/commands/agent_models.rs
@@ -45,12 +45,12 @@ pub async fn get_agent_models(
             .find(|r| r.pubkey == pubkey)
             .ok_or_else(|| format!("agent {pubkey} not found"))?;
 
-        let resolved = resolve_command(&record.acp_command, Some(&app))
+        let resolved = resolve_command(&record.acp_command)
             .ok_or_else(|| missing_command_message(&record.acp_command, "ACP harness command"))?;
 
         let args = normalize_agent_args(&record.agent_command, record.agent_args.clone());
 
-        let resolved_agent = resolve_command(&record.agent_command, Some(&app))
+        let resolved_agent = resolve_command(&record.agent_command)
             .map(|p| p.display().to_string())
             .unwrap_or_else(|| record.agent_command.clone());
 
diff --git a/desktop/src-tauri/src/commands/media.rs b/desktop/src-tauri/src/commands/media.rs
index f27fdb015d..4635c7f836 100644
--- a/desktop/src-tauri/src/commands/media.rs
+++ b/desktop/src-tauri/src/commands/media.rs
@@ -239,7 +239,7 @@ pub async fn upload_media(
 /// (login shell PATH, /opt/homebrew/bin, /usr/local/bin, etc.).
 /// Returns the resolved absolute path on success.
 fn find_ffmpeg() -> Result<std::path::PathBuf, String> {
-    let ffmpeg_path = resolve_command("ffmpeg", None).ok_or_else(|| {
+    let ffmpeg_path = resolve_command("ffmpeg").ok_or_else(|| {
         "ffmpeg is required for video uploads but was not found.\n\n\
          Install it:\n  \
          macOS:   brew install ffmpeg\n  \
diff --git a/desktop/src-tauri/src/managed_agents/discovery.rs b/desktop/src-tauri/src/managed_agents/discovery.rs
index 0fc8a42f94..acb38e9c02 100644
--- a/desktop/src-tauri/src/managed_agents/discovery.rs
+++ b/desktop/src-tauri/src/managed_agents/discovery.rs
@@ -1,8 +1,6 @@
 use std::path::{Path, PathBuf};
 use std::process::Command;
 
-use tauri::AppHandle;
-
 use crate::managed_agents::{
     AcpAvailabilityStatus, AcpProviderCatalogEntry, CommandAvailabilityInfo,
 };
@@ -234,7 +232,7 @@ pub fn normalize_agent_args(command: &str, agent_args: Vec<String>) -> Vec<Strin
     normalized
 }
 
-fn command_search_dirs(app: Option<&AppHandle>) -> Vec<PathBuf> {
+fn command_search_dirs() -> Vec<PathBuf> {
     let mut dirs = vec![
         workspace_root_dir().join("target/release"),
         workspace_root_dir().join("target/debug"),
@@ -262,14 +260,14 @@ fn command_search_dirs(app: Option<&AppHandle>) -> Vec<PathBuf> {
     unique
 }
 
-fn resolve_workspace_command(command: &str, app: Option<&AppHandle>) -> Option<PathBuf> {
+fn resolve_workspace_command(command: &str) -> Option<PathBuf> {
     if command_looks_like_path(command) {
         let path = PathBuf::from(command);
         return path.exists().then_some(path);
     }
 
     let file_name = executable_basename(command);
-    command_search_dirs(app)
+    command_search_dirs()
         .into_iter()
         .map(|dir| dir.join(&file_name))
         .find(|candidate| candidate.exists())
@@ -286,7 +284,7 @@ fn resolve_cache() -> &'static std::sync::Mutex<std::collections::HashMap<String
 /// Resolve a command to an absolute path, caching results for the app lifetime.
 /// The cache eliminates redundant login-shell spawns when multiple agents share
 /// the same binaries (e.g. `npx`, `uvx`).
-pub fn resolve_command(command: &str, app: Option<&AppHandle>) -> Option<PathBuf> {
+pub fn resolve_command(command: &str) -> Option<PathBuf> {
     let cache = resolve_cache();
 
     // Fast path: return cached result without allocating a key.
@@ -297,7 +295,7 @@ pub fn resolve_command(command: &str, app: Option<&AppHandle>) -> Option<PathBuf
     }
 
     // Slow path: resolve and cache.
-    let result = resolve_command_uncached(command, app);
+    let result = resolve_command_uncached(command);
 
     if result.is_some() {
         if let Ok(mut guard) = cache.lock() {
@@ -314,8 +312,8 @@ pub fn clear_resolve_cache() {
     guard.clear();
 }
 
-fn resolve_command_uncached(command: &str, app: Option<&AppHandle>) -> Option<PathBuf> {
-    if let Some(path) = resolve_workspace_command(command, app) {
+fn resolve_command_uncached(command: &str) -> Option<PathBuf> {
+    if let Some(path) = resolve_workspace_command(command) {
         return Some(path);
     }
 
@@ -393,11 +391,11 @@ pub fn login_shell_path() -> Option<String> {
 }
 
 fn find_command(command: &str) -> Option<PathBuf> {
-    resolve_command(command, None)
+    resolve_command(command)
 }
 
-pub fn command_availability(command: &str, app: Option<&AppHandle>) -> CommandAvailabilityInfo {
-    let resolved_path = resolve_command(command, app).map(|path| path.display().to_string());
+pub fn command_availability(command: &str) -> CommandAvailabilityInfo {
+    let resolved_path = resolve_command(command).map(|path| path.display().to_string());
     CommandAvailabilityInfo {
         command: command.to_string(),
         available: resolved_path.is_some(),
diff --git a/desktop/src-tauri/src/managed_agents/runtime.rs b/desktop/src-tauri/src/managed_agents/runtime.rs
index 2cf2951540..7818522bd7 100644
--- a/desktop/src-tauri/src/managed_agents/runtime.rs
+++ b/desktop/src-tauri/src/managed_agents/runtime.rs
@@ -527,19 +527,18 @@ pub fn spawn_agent_child(
         .try_clone()
         .map_err(|error| format!("failed to clone log handle: {error}"))?;
     let agent_args = normalize_agent_args(&record.agent_command, record.agent_args.clone());
-    let resolved_acp_command = resolve_command(&record.acp_command, Some(app))
+    let resolved_acp_command = resolve_command(&record.acp_command)
         .ok_or_else(|| missing_command_message(&record.acp_command, "ACP harness command"))?;
-    let resolved_mcp_command: Option<std::path::PathBuf> = if record.mcp_command.is_empty() {
-        None
-    } else {
-        Some(
-            resolve_command(&record.mcp_command, Some(app)).ok_or_else(|| {
+    let resolved_mcp_command: Option<std::path::PathBuf> =
+        if record.mcp_command.is_empty() {
+            None
+        } else {
+            Some(resolve_command(&record.mcp_command).ok_or_else(|| {
                 missing_command_message(&record.mcp_command, "MCP server command")
-            })?,
-        )
-    };
+            })?)
+        };
     // Resolve agent command to a full path (DMG launches have minimal PATH).
-    let resolved_agent_command = resolve_command(&record.agent_command, Some(app))
+    let resolved_agent_command = resolve_command(&record.agent_command)
         .map(|p| p.display().to_string())
         .unwrap_or_else(|| record.agent_command.clone());
 
@@ -698,7 +697,7 @@ pub fn spawn_agent_child(
     // interfere with other remotes (e.g. GitHub).
     //
     // NOSTR_PRIVATE_KEY mirrors SPROUT_PRIVATE_KEY — keep in sync.
-    if let Some(cred_helper) = resolve_command("git-credential-nostr", Some(app)) {
+    if let Some(cred_helper) = resolve_command("git-credential-nostr") {
         let relay_http_url = crate::relay::relay_http_base_url(&record.relay_url);
 
         command.env("NOSTR_PRIVATE_KEY", &record.private_key_nsec);
diff --git a/desktop/src-tauri/src/managed_agents/storage.rs b/desktop/src-tauri/src/managed_agents/storage.rs
index ae3ac62047..265224d660 100644
--- a/desktop/src-tauri/src/managed_agents/storage.rs
+++ b/desktop/src-tauri/src/managed_agents/storage.rs
@@ -181,8 +181,11 @@ pub fn read_log_tail(path: &Path, max_lines: usize) -> Result<String, String> {
         newline_count = bytecount_newlines(&buf);
     }
 
-    let text = String::from_utf8_lossy(&buf);
-    let lines: Vec<&str> = text.lines().collect();
+    // Strip ANSI escapes here (not in the harness) so the desktop log view
+    // renders cleanly while terminals and other tools still get the colors
+    // sprout-acp emits.
+    let cleaned = strip_ansi_escapes::strip_str(&String::from_utf8_lossy(&buf));
+    let lines: Vec<&str> = cleaned.lines().collect();
     let start = lines.len().saturating_sub(max_lines);
     Ok(lines[start..].join("\n"))
 }
@@ -190,3 +193,15 @@ pub fn read_log_tail(path: &Path, max_lines: usize) -> Result<String, String> {
 fn bytecount_newlines(buf: &[u8]) -> usize {
     buf.iter().filter(|&&b| b == b'\n').count()
 }
+
+#[cfg(test)]
+mod tests {
+    #[test]
+    fn strips_ansi_from_typical_tracing_line() {
+        let input = "\x1b[2m2026-05-27T15:16:32\x1b[0m \x1b[32m INFO\x1b[0m \x1b[2msprout_acp\x1b[0m\x1b[2m:\x1b[0m starting";
+        assert_eq!(
+            strip_ansi_escapes::strip_str(input),
+            "2026-05-27T15:16:32  INFO sprout_acp: starting"
+        );
+    }
+}
