From 17f828f159bcd096fd791f3944aceea508c70618 Mon Sep 17 00:00:00 2001 From: Tara Menon Date: Fri, 12 Jun 2026 19:05:35 +0530 Subject: [PATCH] fix(browser): platform-aware error messages + corrupted download detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause on macOS: 'camoufox-browser install' downloaded v150.0.2-alpha.26 instead of the stable browser binary. This alpha version is missing libmozglue.dylib, causing dyld to crash immediately. Changes: 1. detect_os() — returns Mac/Linux/Wsl/Windows enum for platform-aware logic 2. check_known_errors() — parses daemon stderr for known failures (missing libmozglue.dylib on macOS, missing libgtk/libnss on Linux, code-signing) 3. platform_fix_instructions() — returns OS-specific recovery steps: - macOS: rm -rf ~/Library/Caches/camoufox, reinstall, or Docker fallback - WSL/Linux: correct apt-get install commands (with t64 package variants) 4. Browser cache detection now checks Camoufox.app on macOS, camoufox on Linux 5. All error messages use detect_os() — no more Linux apt-get on macOS 6. Corrupted download detected automatically, user told to clear cache + retry Clean build: 0 errors, 0 warnings --- crates/gyandoot-agent/src/browser_manager.rs | 238 ++++++++++++++----- 1 file changed, 176 insertions(+), 62 deletions(-) diff --git a/crates/gyandoot-agent/src/browser_manager.rs b/crates/gyandoot-agent/src/browser_manager.rs index 706e539..880cff1 100644 --- a/crates/gyandoot-agent/src/browser_manager.rs +++ b/crates/gyandoot-agent/src/browser_manager.rs @@ -19,11 +19,6 @@ //! The daemon is started via Node.js's `npx`. Resolution order: //! 1. `config.browser.node_path` — explicit path in config //! 2. `"npx"` on Linux/macOS, `"npx.cmd"` on Windows — PATH lookup -//! -//! WSL Detection -//! -//! Windows Subsystem for Linux is detected by reading `/proc/version`. -//! WSL-specific error messages guide users to install Node.js inside WSL. use std::time::Duration; @@ -50,6 +45,37 @@ pub enum BrowserInitResult { Unavailable(String), } +/// Platform category for platform-specific error messages. +#[derive(Debug, PartialEq)] +enum OsKind { + Mac, + Linux, + Windows, + Wsl, +} + +/// Detect what operating system we're running on. +fn detect_os() -> OsKind { + if cfg!(target_os = "windows") { + return OsKind::Windows; + } + if cfg!(target_os = "macos") { + return OsKind::Mac; + } + // Linux — check for WSL + if std::fs::read_to_string("/proc/version") + .map(|c| { + let l = c.to_lowercase(); + l.contains("microsoft") || l.contains("wsl") + }) + .unwrap_or(false) + { + OsKind::Wsl + } else { + OsKind::Linux + } +} + /// Resolve the npx executable name, respecting config and platform. fn resolve_npx(node_path: Option<&str>) -> &str { if let Some(p) = node_path { @@ -64,36 +90,113 @@ fn resolve_npx(node_path: Option<&str>) -> &str { } } -/// Detect if we're running inside Windows Subsystem for Linux (WSL). -fn is_wsl() -> bool { - std::fs::read_to_string("/proc/version") - .map(|content| { - let lower = content.to_lowercase(); - lower.contains("microsoft") || lower.contains("wsl") - }) - .unwrap_or(false) -} - /// Get a platform-aware example npx path for error messages. fn example_npx_path() -> &'static str { - if cfg!(target_os = "macos") { - "\"/Users/yourname/.nvm/versions/node/v22/bin/npx\"" - } else { - "\"/home/user/.nvm/versions/node/v22/bin/npx\"" + match detect_os() { + OsKind::Mac => "\"/Users/yourname/.nvm/versions/node/v22/bin/npx\"", + _ => "\"/home/user/.nvm/versions/node/v22/bin/npx\"", } } /// Path to the Camoufox browser binary cache directory. -/// This is where `camoufox-browser install` downloads the ~662MB browser. +/// macOS: ~/Library/Caches/camoufox/ +/// Linux/WSL: ~/.cache/camoufox/ fn camoufox_browser_cache_dir() -> std::path::PathBuf { let home = dirs::home_dir().unwrap_or_else(|| std::path::PathBuf::from("/tmp")); - home.join(".cache").join("camoufox") + if cfg!(target_os = "macos") { + home.join("Library").join("Caches").join("camoufox") + } else { + home.join(".cache").join("camoufox") + } } /// Check if the Camoufox browser binary is already installed. fn is_browser_installed() -> bool { let cache_dir = camoufox_browser_cache_dir(); - cache_dir.exists() && cache_dir.join("camoufox").exists() + let app_bundle = cache_dir.join("Camoufox.app"); + let binary = cache_dir.join("camoufox"); + + if cfg!(target_os = "macos") { + // macOS: binary lives inside Camoufox.app/Contents/MacOS/camoufox + app_bundle.exists() && app_bundle.join("Contents").join("MacOS").join("camoufox").exists() + } else { + // Linux/WSL: binary at ~/.cache/camoufox/camoufox + binary.exists() + } +} + +/// Check if the browser binary appears corrupted (missing critical dylib). +/// Looks for common indicators in the daemon log. +fn check_known_errors(log: &str) -> Option { + let lower = log.to_lowercase(); + + // macOS: missing libmozglue.dylib — corrupted download + if lower.contains("libmozglue.dylib") && lower.contains("no such file") { + return Some( + "The Camoufox browser download appears corrupted (missing libmozglue.dylib). \ + This can happen with unstable alpha versions." + .to_string(), + ); + } + + // Linux: missing libgtk / libnss + if lower.contains("error while loading shared libraries") { + if lower.contains("libgtk") || lower.contains("libnss") || lower.contains("libasound") { + return Some( + "Missing Linux system libraries needed by the Camoufox browser." + .to_string(), + ); + } + } + + // macOS: code signing issue + if lower.contains("killed") && lower.contains("code signature") { + return Some( + "macOS code signing issue. The downloaded browser binary may need manual approval." + .to_string(), + ); + } + + None +} + +/// Get platform-specific dependency fix instructions. +fn platform_fix_instructions() -> &'static str { + match detect_os() { + OsKind::Mac => "\ + \u{2192} The Camoufox browser download was corrupted. Fix:\n \ + 1. Remove the corrupted cache:\n \ + rm -rf ~/Library/Caches/camoufox\n \ + 2. Reinstall the browser binary:\n \ + npx camoufox-browser@latest install\n \ + 3. The download is ~662MB. If it still fails, try:\n \ + npx camoufox-browser@latest install 2>&1 | tail -20\n \ + to see the real download error.\n \ + 4. Alternative: use Docker instead:\n \ + docker run -d --name camoufox -p 9377:9377 \\\n \ + ghcr.io/nousresearch/camoufox-browser:latest", + OsKind::Wsl => "\ + \u{2192} Missing Linux dependencies on WSL. Install them:\n \ + sudo apt-get update && sudo apt-get install -y \\\n \ + libgtk-3-0 libnss3 libnspr4 libatk1.0-0t64 \\\n \ + libatk-bridge2.0-0t64 libcups2t64 libdrm2 \\\n \ + libxkbcommon0 libxcomposite1 libxdamage1 \\\n \ + libxfixes3 libxrandr2 libgbm1 libpango-1.0-0 \\\n \ + libcairo2 libasound2t64 libxcb-shm0 \\\n \ + libx11-xcb1 libx11-6 libxcb1 libxext6 \\\n \ + libxcursor1 libxi6 libfreetype6 libfontconfig1", + OsKind::Linux => "\ + \u{2192} Missing Linux dependencies. Install them:\n \ + sudo apt-get update && sudo apt-get install -y \\\n \ + libgtk-3-0 libnss3 libnspr4 libatk1.0-0t64 \\\n \ + libatk-bridge2.0-0t64 libcups2t64 libdrm2 \\\n \ + libxkbcommon0 libxcomposite1 libxdamage1 \\\n \ + libxfixes3 libxrandr2 libgbm1 libpango-1.0-0 \\\n \ + libcairo2 libasound2t64 libxcb-shm0", + OsKind::Windows => "\ + \u{2192} Windows issue. Try reinstalling camoufox-browser:\n \ + npm install -g camoufox-browser", + } } /// Initialize the camoufox-browser daemon and return a ready client. @@ -120,18 +223,26 @@ pub async fn init_browser(node_path: Option<&str>) -> BrowserInitResult { Ok(msg) => info!("Camoufox browser install: {}", msg), Err(e) => { let npx_name = resolve_npx(node_path); - let wsl_hint = if is_wsl() { - "\n\n \u{26a0} WSL detected: You may need to install system deps first. Run:\n sudo apt-get update && sudo apt-get install -y libgtk-3-0 libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxfixes3 libxrandr2 libgbm1 libpango-1.0-0 libcairo2 libasound2" - } else { - "" + let os_type = detect_os(); + let platform_hint = match os_type { + OsKind::Wsl => "\n\n \u{26a0} WSL detected: Make sure you're not behind a proxy/firewall that blocks \ + the GitHub release download. The browser binary is ~662MB.\n \ + Try: curl -L https://github.com/msly/camoufox-browser/releases/download/v0.1.0/camoufox-browser-linux-x64 -o /dev/null" + .to_string(), + OsKind::Mac => "\n\n \u{26a0} macOS: The download server may be slow or unstable. Try:\n \ + npx camoufox-browser@latest install --verbose\n \ + If it keeps failing, use Docker instead:\n \ + docker run -d --name camoufox -p 9377:9377 \\\n \ + ghcr.io/nousresearch/camoufox-browser:latest" + .to_string(), + _ => String::new(), }; return BrowserInitResult::Unavailable(format!( "Failed to download Camoufox browser: {}\n\n\ Run this manually to install:\n \ {} camoufox-browser@latest install\n\n\ - You need ~662MB of free disk space for the browser binary.\n\ - After install completes, run 'gyandoot run' again.{}", - e, npx_name, wsl_hint + You need ~662MB of free disk space for the browser binary.{}", + e, npx_name, platform_hint )); } } @@ -151,21 +262,13 @@ pub async fn init_browser(node_path: Option<&str>) -> BrowserInitResult { } Ok(false) => { let msg = format!( - "Daemon did not start within {}s \u{2014} socket not found at {}\n\ - This usually means:\n \ - 1. Missing Linux system deps (WSL/Ubuntu):\n \ - sudo apt-get update && sudo apt-get install -y \\\n \ - libgtk-3-0 libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 \\\n \ - libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 \\\n \ - libxfixes3 libxrandr2 libgbm1 libpango-1.0-0 libcairo2 \\\n \ - libasound2 libxcb-shm0 libx11-xcb1 libx11-6 libxcb1 \\\n \ - libxext6 libxcursor1 libxi6 libfreetype6 libfontconfig1\n \ - 2. Try starting manually to see the error:\n \ - {} camoufox-browser@latest tab_list --session default\n\n\ - Check the daemon log at the path above for details.", + "Camoufox daemon did not start within {}s \u{2014} socket not found at {}\n\n\ + The browser binary may be corrupted. Try fixing it:\n\ + {}\n\n\ + Then run 'gyandoot run' again.", DAEMON_STARTUP_TIMEOUT_SECS, socket_path.display(), - resolve_npx(node_path), + platform_fix_instructions(), ); warn!("{}", msg); BrowserInitResult::Unavailable(msg) @@ -178,25 +281,38 @@ pub async fn init_browser(node_path: Option<&str>) -> BrowserInitResult { } } Err(e) => { + let error_detail = e.to_string(); let npx_name = resolve_npx(node_path); - let msg = format!( - "Could not start camoufox daemon: {}\n\n\ - The npm package 'camoufox-browser' is installed, and the browser\n\ - binary has been downloaded, but the daemon failed to start.\n\n\ - Try running this manually to see the real error:\n \ - {} camoufox-browser@latest tab_list --session default\n\n\ - Common fixes:\n \ - 1. Install missing system dependencies (Linux/WSL):\n \ - sudo apt-get update && sudo apt-get install -y \\\n \ - libgtk-3-0 libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 \\\n \ - libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 \\\n \ - libxfixes3 libxrandr2 libgbm1 libpango-1.0-0 libcairo2 \\\n \ - libasound2\n \ - 2. If Node is via nvm/fnm/asdf, set browser.node_path in config:\n \ - [browser]\n \ - node_path = {}", - e, npx_name, example_npx_path() + + // Check for known errors in the daemon output + let known_error = check_known_errors(&error_detail); + + // Build the full message + let mut msg = format!( + "\u{26a0} Camoufox daemon failed to start.\n\n\ + Error:\n {}\n\n", + error_detail, ); + + if let Some(hint) = known_error { + msg.push_str(&format!("Detected: {}\n\n", hint)); + msg.push_str(platform_fix_instructions()); + } else { + msg.push_str(&format!( + "Try running this manually to see the full error:\n \ + {} camoufox-browser@latest tab_list --session default\n\n\ + Common fixes:\n\ + {}\n\n\ + 3. If Node is via nvm/fnm/asdf, set browser.node_path in config:\n \ + [browser]\n \ + node_path = {}", + npx_name, + platform_fix_instructions(), + example_npx_path() + )); + } + + msg.push_str("\n\nThen run 'gyandoot run' again."); warn!("{}", msg); BrowserInitResult::Unavailable(msg) } @@ -249,7 +365,6 @@ async fn trigger_daemon_start(session: &str, node_path: Option<&str>) -> Result< let npx = resolve_npx(node_path); - // Run a command — the CLI auto-starts the daemon as a side effect let output = Command::new(npx) .args(&[ "camoufox-browser@latest", @@ -283,8 +398,7 @@ async fn trigger_daemon_start(session: &str, node_path: Option<&str>) -> Result< )); return Err(format!( - "camoufox-browser daemon start failed (exit: {}):\n {}\n \u{2192} Full log saved to {}", - output.status.code().unwrap_or(-1), + "{}\n \u{2192} Full log saved to {}", details, log_file.display(), ));