From 9851e4118292f766a90e3a676706b31caac0b023 Mon Sep 17 00:00:00 2001 From: Tara Menon Date: Sat, 13 Jun 2026 08:17:38 +0530 Subject: [PATCH] fix(browser): robust fetch with incomplete-cache cleanup + retry + binary verification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause: camoufox-js@0.11.0 fetch can exit 0 but silently fail to download the browser binary when: 1. A partial/incomplete cache from a previous run exists 2. GitHub API rate-limits the unauthenticated release query 3. Network drops mid-download Fix: 1. run_fetch() now DELETE incomplete cache (dir exists but no version.json) before fetching — prevents 'already cached' no-op 2. After fetch, VERIFY version.json exists — if missing, RETRY once 3. After retry, verify Camoufox.app/Contents/MacOS/camoufox exists 4. Log stdout and stderr in INFO traces for debugging --- crates/gyandoot-agent/src/browser_manager.rs | 65 +++++++++++++++++++- 1 file changed, 62 insertions(+), 3 deletions(-) diff --git a/crates/gyandoot-agent/src/browser_manager.rs b/crates/gyandoot-agent/src/browser_manager.rs index 573f396..d0a710d 100644 --- a/crates/gyandoot-agent/src/browser_manager.rs +++ b/crates/gyandoot-agent/src/browser_manager.rs @@ -243,15 +243,30 @@ pub async fn init_browser(node_path: Option<&str>) -> BrowserInitResult { /// Fetch the browser binary using pinned camoufox-js. async fn run_fetch(npx: &str, js_version: &str) -> Result { + let cache = browser_cache_dir(); + + // If cache dir exists but is incomplete, nuke it first + // (partial downloads from prior aborted runs can confuse camoufox-js) + if cache.exists() && !cache.join("version.json").exists() { + info!("Removing incomplete cache at {}", cache.display()); + let _ = std::fs::remove_dir_all(&cache); + info!("Will re-download fresh"); + } + let output = Command::new(npx) - .args(&["--yes", &format!("camoufox-js@{}", js_version), "fetch"]) + .args(&["--yes", &format!("camoufox-js@{js_version}"), "fetch"]) .output() .await - .map_err(|e| format!("'{} camoufox-js' not found: {}", npx, e))?; + .map_err(|e| format!("'{npx} camoufox-js' not found: {e}"))?; let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string(); let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string(); + info!("camoufox-js fetch stdout: {}", stdout.lines().last().unwrap_or("(empty)")); + if !stderr.is_empty() { + info!("camoufox-js fetch stderr: {}", stderr.lines().last().unwrap_or("(empty)")); + } + if !output.status.success() { let detail = if !stderr.is_empty() { stderr.lines().filter(|l| l.contains("Error") || l.contains("error")).last().unwrap_or("(check output)") @@ -261,7 +276,51 @@ async fn run_fetch(npx: &str, js_version: &str) -> Result { return Err(format!("exit {}: {}", output.status.code().unwrap_or(-1), detail)); } - Ok(stdout.lines().filter(|l| l.contains("complete") || l.contains("Installed") || l.contains("Downloaded")) + // Verify the download actually produced version.json + if !cache.join("version.json").exists() { + warn!("camoufox-js exited 0 but version.json not found at {}", cache.display()); + warn!("This usually means the GitHub release download failed. Retrying once..."); + + // Retry once with full output visible + let retry_output = Command::new(npx) + .args(&["--yes", &format!("camoufox-js@{js_version}"), "fetch"]) + .output() + .await + .map_err(|e| format!("Retry failed: {e}"))?; + + let retry_stdout = String::from_utf8_lossy(&retry_output.stdout).trim().to_string(); + let retry_stderr = String::from_utf8_lossy(&retry_output.stderr).trim().to_string(); + + info!("Retry stdout: {}", retry_stdout.lines().last().unwrap_or("(empty)")); + if !retry_stderr.is_empty() { + info!("Retry stderr: {}", retry_stderr.lines().last().unwrap_or("(empty)")); + } + + if !retry_output.status.success() || !cache.join("version.json").exists() { + return Err(format!( + "Download failed after retry.\n stdout: {}\n stderr: {}\n cache: {}\n\nThis is likely a network/GitHub rate-limit issue.\nTry manually:\n export GITHUB_TOKEN=your_token\n {} --yes camoufox-js@{js_version} fetch", + retry_stdout.lines().last().unwrap_or("(empty)"), + retry_stderr.lines().last().unwrap_or("(empty)"), + cache.display(), + npx, + )); + } + } + + // Verify the binary exists too + let binary_ok = if cfg!(target_os = "macos") { + cache.join("Camoufox.app").join("Contents").join("MacOS").join("camoufox").exists() + } else { + cache.join("camoufox").exists() + }; + + if !binary_ok { + warn!("version.json exists but browser binary missing — possibly corrupted download"); + } + + Ok(stdout.lines() + .chain(stderr.lines()) + .filter(|l| l.contains("complete") || l.contains("Installed") || l.contains("Downloaded") || l.contains("Camoufox")) .last() .unwrap_or("Fetch completed") .to_string())