libnyanpasu/clash-nyanpasu · error
Clash stream actor timed out
Error message
Clash stream actor timed out
What it means
The Clash websocket/IPC bridge sends a typed message to a ractor actor with a 10-second RPC timeout and maps CallResult::Timeout to this error. It means the Clash stream actor received the request but did not reply within 10 seconds — the actor is busy, blocked, or its handler hung. Distinguish it from 'actor unavailable' (send failure) and 'reply dropped' (sender error).
Source
Thrown at backend/tauri/src/core/clash/ws.rs:695
Ok(Self(Arc::new(Inner {
actor,
connections,
events,
})))
}
async fn call<T: Send + 'static>(
&self,
message: impl FnOnce(RpcReplyPort<T>) -> Message,
) -> Result<T> {
match self
.0
.actor
.call(message, Some(Duration::from_secs(10)))
.await
.context("Clash stream actor unavailable")?
{
CallResult::Success(value) => Ok(value),
CallResult::Timeout => anyhow::bail!("Clash stream actor timed out"),
CallResult::SenderError => anyhow::bail!("Clash stream actor reply dropped"),
}
}
pub async fn start(&self) -> Result<()> {
self.call(Message::Start).await
}
#[allow(dead_code)]
pub async fn stop(&self) -> Result<()> {
self.call(Message::Stop).await
}
pub async fn snapshot(&self) -> Result<ClashWsSnapshot> {
self.call(Message::Snapshot).await
}
pub async fn set_recording(
&self,
kind: ClashWsKind,
enabled: bool,
) -> Result<ClashWsRecording> {View on GitHub (pinned to f7dbce2997)
Solutions
- Check the Clash core process is alive and the websocket endpoint is responsive; restart the core if hung
- Increase the 10s timeout only if the operation is legitimately slow — prefer diagnosing the stall first
- Inspect the actor's message loop for blocking calls or cross-actor cycles that can deadlock (avoid StateActor -> CoreActor -> StateActor patterns)
- Add retry-with-backoff at the caller for idempotent messages like Start
Example fix
// before
.actor.call(message, Some(Duration::from_secs(10)))
// after
match actor.call(message, Some(Duration::from_secs(10))).await {
Ok(CallResult::Timeout) => log::warn!("clash stream actor timed out; retrying once"),
other => return other.context("Clash stream actor unavailable"),
} Defensive patterns
Strategy: retry
Validate before calling
// before calling: check core is reachable // e.g. ping the clash controller endpoint with a short HTTP timeout
Try / catch
match client.start().await {
Err(e) if e.to_string().contains("timed out") => retry_with_backoff(3, || client.start()),
other => other,
} Prevention
- Keep the Clash core process healthy and monitored
- Avoid blocking calls or cross-actor cycles inside the actor handler
- Use the actor's own timeout instead of wrapping in outer cancellation-prone timeouts
When it happens
Trigger: Calling start() (or other Message variants) on the clash ws client while the actor is stuck processing a prior message, the backend connection is stalled, or the actor task is starved on a blocked await for more than 10 seconds.
Common situations: Clash core hanging or slow to accept a websocket connection; network stalls to the core endpoint; actor deadlocked on a synchronous cross-actor call; heavy event loop under load.
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.
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- application actor call timed out
- clash config actor call timed out
- session state actor call timed out
- clash config actor reply dropped
- session state actor reply dropped
AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08).
Data as JSON: /api/errors/2a5ee771a8192b06.
Report an issue: GitHub.