libnyanpasu/clash-nyanpasu · warning
Clash stream actor reply dropped
Error message
Clash stream actor reply dropped
What it means
The ractor RPC returned CallResult::SenderError, meaning the reply channel's sender was gone when the actor tried to answer — the caller's oneshot receiver was dropped mid-request. In practice this indicates the request future was cancelled/dropped before the actor replied, or the actor framework failed to deliver the response. Distinct from timeout: the actor did (try to) reply, but nobody was listening.
Source
Thrown at backend/tauri/src/core/clash/ws.rs:696
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> {
self.call(|reply| Message::Recording(kind, enabled, reply))View on GitHub (pinned to f7dbce2997)
Solutions
- Ensure callers await the call to completion or handle cancellation gracefully; avoid wrapping in select! that drops the call future mid-flight on timeouts
- Log at debug level and surface as retryable; resend the message once for idempotent operations
- Check actor reply code uses the correct RpcReplyPort and that State is not dropping ports early
Example fix
// before
let result = tokio::select! {
r = client.start() => r?,
_ = sleep(TIMEOUT) => bail!("outer timeout"),
};
// after
let result = client.start().await?; // let the actor's own 10s RPC timeout govern cancellation Defensive patterns
Strategy: try-catch
Try / catch
match client.start().await {
Err(e) if e.to_string().contains("reply dropped") => log::debug!("request cancelled before reply; safe to resend"),
other => other?,
} Prevention
- Don't drop/abort the call future mid-flight (avoid select! cancellation of actor RPCs)
- Await actor calls to completion or implement explicit resend for idempotent messages
When it happens
Trigger: The future calling .actor.call(...) is aborted (task cancelled, request dropped, timeout wrapper cancelling the outer future) before the actor sends its reply; the reply port's receiver side was dropped.
Common situations: Tauri command handlers cancelled by the frontend (window closed / request aborted); tokio tasks aborted on shutdown; racing timeouts in an outer layer that drops the inner call future.
Related errors
- application actor call timed out
- clash config actor reply dropped
- clash config actor call timed out
- session state actor reply dropped
- session state actor call timed out
AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08).
Data as JSON: /api/errors/2f3aa8c4aa3646c7.
Report an issue: GitHub.