libnyanpasu/clash-nyanpasu · error

failed to download PAC script after {} attempts

Error message

failed to download PAC script after {} attempts

What it means

This is the terminal error returned by download_pac_script after all PAC_MAX_RETRIES attempts have failed. It wraps the last underlying error (last_error); if that is somehow None it is produced standalone. It signals that no retry remains and the caller must handle PAC unavailability.

Source

Thrown at backend/tauri/src/core/pac.rs:71

                        log::warn!(target: "app", "Attempt {}/{} failed: {}", attempt, PAC_MAX_RETRIES, err);
                        last_error = Some(err);
                    }
                }
                Err(e) => {
                    let err = anyhow::anyhow!("failed to download PAC script: {}", e);
                    log::warn!(target: "app", "Attempt {}/{} failed: {}", attempt, PAC_MAX_RETRIES, err);
                    last_error = Some(err);
                }
            }

            // Wait before retrying (except on last attempt)
            if attempt < PAC_MAX_RETRIES {
                tokio::time::sleep(Duration::from_secs(PAC_RETRY_DELAY)).await;
            }
        }

        Err(last_error.unwrap_or_else(|| {
            anyhow::anyhow!(
                "failed to download PAC script after {} attempts",
                PAC_MAX_RETRIES
            )
        }))
    }

    /// Save PAC script to cache directory
    pub async fn save_pac_script(script: &str) -> Result<PathBuf> {
        let cache_dir = crate::utils::dirs::cache_dir()?;
        let pac_file = cache_dir.join("pac.js");

        fs::write(&pac_file, script)
            .await
            .context("failed to save PAC script")?;

        Ok(pac_file)
    }

View on GitHub (pinned to f7dbce2997)

Solutions

  1. Inspect the wrapped cause (last_error) printed in earlier 'Attempt N/M failed' warnings to find the root cause
  2. Fix the underlying network/URL problem identified in the wrapped error
  3. Ensure the app's fallback path (fallback_to_direct_proxy) runs so the system proxy still works
  4. Increase PAC_MAX_RETRIES or PAC_RETRY_DELAY for unreliable networks
  5. Cache a previously downloaded PAC script locally and use it when download fails

Example fix

// caller-side handling
match Pac::download_pac_script().await {
    Ok(script) => { /* install script */ }
    Err(e) => {
        log::warn!("PAC unavailable: {e}; falling back to direct proxy");
        Pac::fallback_to_direct_proxy()?;
    }
}
Defensive patterns

Strategy: fallback

Try / catch

match download_pac_script().await {
    Ok(script) => install(script),
    Err(e) => {
        log::warn!("PAC unavailable after retries: {e}; falling back to direct proxy");
        fallback_to_direct_proxy().unwrap_or_else(|fe| log::error!("no system proxy either: {fe}"));
    }
}

Prevention

When it happens

Trigger: Calling download_pac_script when every attempt fails with transport errors, non-2xx statuses, or body-read failures — after PAC_MAX_RETRIES attempts separated by PAC_RETRY_DELAY seconds.

Common situations: Prolonged server outage or network disconnection during startup; permanently wrong PAC URL; server blocking the client; offline laptop starting the app with no connectivity.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08). Data as JSON: /api/errors/eca8044ef7838813. Report an issue: GitHub.