fix(browser): robust fetch with incomplete-cache cleanup + retry + binary verification

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
This commit is contained in:
2026-06-13 08:17:38 +05:30
parent 68f7629f6f
commit 9851e41182

View File

@@ -243,15 +243,30 @@ pub async fn init_browser(node_path: Option<&str>) -> BrowserInitResult {
/// Fetch the browser binary using pinned camoufox-js. /// Fetch the browser binary using pinned camoufox-js.
async fn run_fetch(npx: &str, js_version: &str) -> Result<String, String> { async fn run_fetch(npx: &str, js_version: &str) -> Result<String, String> {
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) let output = Command::new(npx)
.args(&["--yes", &format!("camoufox-js@{}", js_version), "fetch"]) .args(&["--yes", &format!("camoufox-js@{js_version}"), "fetch"])
.output() .output()
.await .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 stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
let stderr = String::from_utf8_lossy(&output.stderr).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() { if !output.status.success() {
let detail = if !stderr.is_empty() { let detail = if !stderr.is_empty() {
stderr.lines().filter(|l| l.contains("Error") || l.contains("error")).last().unwrap_or("(check output)") 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<String, String> {
return Err(format!("exit {}: {}", output.status.code().unwrap_or(-1), detail)); 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() .last()
.unwrap_or("Fetch completed") .unwrap_or("Fetch completed")
.to_string()) .to_string())