libnyanpasu/clash-nyanpasu · error

proxy actor timed out; an operation may still be running, do

Error message

proxy actor timed out; an operation may still be running, do not replay mutations automatically

What it means

Thrown by the proxies service `call` helper when a request/reply call to the underlying ractor proxy actor times out (120 seconds). The actor may still be processing the operation in the background, so callers are explicitly warned not to automatically replay the mutation — a retry could double-apply it. This is a timeout guard against a stuck or overloaded actor rather than a definitive failure of the operation.

Source

Thrown at backend/tauri/src/core/proxies.rs:328

        .await?;
        Ok(Self(Arc::new(ClientInner {
            actor,
            snapshots: snapshot_rx,
            changes: changes_rx,
        })))
    }
    async fn call<T: Send + 'static>(
        &self,
        message: impl FnOnce(RpcReplyPort<Result<T>>) -> Message,
    ) -> Result<T> {
        match self
            .0
            .actor
            .call(message, Some(Duration::from_secs(120)))
            .await
        {
            Ok(ractor::rpc::CallResult::Success(result)) => result,
            Ok(ractor::rpc::CallResult::Timeout) => anyhow::bail!(
                "proxy actor timed out; an operation may still be running, do not replay mutations automatically"
            ),
            _ => anyhow::bail!("proxy actor is unavailable"),
        }
    }
    pub async fn get(&self, force: bool) -> Result<Proxies> {
        let snapshot = self.call(|reply| Message::Read { force, reply }).await?;
        anyhow::ensure!(
            !snapshot.api.is_revoked(),
            "proxy snapshot belongs to a retired instance"
        );
        Ok(snapshot.proxies.clone())
    }
    pub async fn providers(&self) -> Result<api::ProvidersProxiesRes> {
        let snapshot = self
            .call(|reply| Message::Read {
                force: false,
                reply,

View on GitHub (pinned to f7dbce2997)

Solutions

  1. Do not auto-retry mutations; first check current state with a read operation (`get`) to see whether the original operation actually completed.
  2. Retry the read/idempotent operations freely; for `select`/`update_provider`, wait and verify state before re-issuing manually.
  3. If the actor is persistently stuck, restart the core/actor via the app's restart mechanism and check network reachability of provider URLs.

Example fix

// before: blind retry on any error
match client.update_provider(name).await {
    Err(_) => client.update_provider(name).await?, // may double-apply
    Ok(v) => v,
}

// after: timeout means 'may still be running' — verify first
match client.update_provider(name).await {
    Err(e) if e.to_string().contains("timed out") => {
        let snapshot = client.get_proxies().await?; // check state before manual retry
    }
    other => other?,
}
Defensive patterns

Strategy: retry

Validate before calling

// Cheap liveness probe with a short timeout before long mutations
match tokio::time::timeout(
    Duration::from_secs(5),
    client.get_proxies(),
).await {
    Ok(Ok(_)) => {} // actor responsive, safe to proceed
    _ => eprintln!("proxy actor slow or stuck; avoid mutating calls now"),
}

Try / catch

match client.update_provider(name).await {
    Err(e) if e.to_string().contains("proxy actor timed out") => {
        // do NOT auto-retry; verify state, then retry manually with backoff
        let state = client.get_proxies().await?;
    }
    other => other?,
}

Prevention

When it happens

Trigger: Any of `get`, `providers`, `select`, or `update_provider` calling `call(...)` when `actor.call(message, Some(Duration::from_secs(120)))` returns `ractor::rpc::CallResult::Timeout` — i.e. the proxy actor did not reply within 120 seconds.

Common situations: A provider update/download is slow or hung (large rule/provider download, stalled network); the actor is blocked on a long sequential operation ahead of yours; the core API is unresponsive while the actor waits on it.

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/1c7ce7fcb495381a. Report an issue: GitHub.