libnyanpasu/clash-nyanpasu · error
session state actor reply dropped
Error message
session state actor reply dropped
What it means
Raised in SessionStateClient::prepare_replace (backend/tauri/src/client/session_state.rs:113) when the ractor RPC for SessionStateActorMessage::PrepareReplace returns CallResult::SenderError, meaning the actor dropped the reply sender without answering — typically because the actor was stopped, panicked, or its handler failed before replying.
Source
Thrown at backend/tauri/src/client/session_state.rs:113
self.replace_prepared_if_version(expected_version, prepared)
.await
}
pub(crate) async fn prepare_replace(
&self,
state: PersistentState,
) -> anyhow::Result<PreparedTypedReplace<PersistentState>> {
match self
.inner
.actor_ref
.call(
|reply| SessionStateActorMessage::PrepareReplace { state, reply },
None,
)
.await?
{
CallResult::Success(result) => result,
CallResult::SenderError => anyhow::bail!("session state actor reply dropped"),
CallResult::Timeout => anyhow::bail!("session state actor call timed out"),
}
}
pub(crate) async fn replace_prepared_if_version(
&self,
expected_version: u64,
prepared: PreparedTypedReplace<PersistentState>,
) -> anyhow::Result<ConditionalReplaceResult<SessionStateSnapshot>> {
match self
.inner
.actor_ref
.call(
|reply| SessionStateActorMessage::ReplacePreparedIfVersion {
expected_version,
prepared,
reply,
},View on GitHub (pinned to f7dbce2997)
Solutions
- Confirm the SessionStateActor is running (not stopped) before issuing prepare_replace.
- Retry the operation; if it follows an actor stop, recreate the client/actor pair.
- Check actor handler logs for panics or early returns that skip the reply.
- Ensure client Drop ordering does not stop the actor while other holders still call it.
Example fix
// before
let prepared = client.prepare_replace(state).await?; // may hit dropped reply during shutdown
// after
if client.is_running().await {
let prepared = client.prepare_replace(state).await?;
} Defensive patterns
Strategy: try-catch
Try / catch
match client.prepare_replace(state).await {
Ok(p) => p,
Err(e) if e.to_string().contains("reply dropped") => {
// actor likely stopped: respawn/rebind, then retry once
let client = supervisor.rebind_session_state().await?;
client.prepare_replace(state).await?
}
Err(e) => return Err(e),
} Prevention
- Serialize actor shutdown so stop happens only after in-flight client calls drain
- Check handler code paths for early returns that skip the RpcReplyPort send
- Wrap actor handlers so panics are caught and answered with an error reply
- Re-read state after a dropped-reply error before retrying writes
When it happens
Trigger: replace_if_version calls prepare_replace; the actor_ref.call resolves to CallResult::SenderError, i.e. the RpcReplyPort was dropped by the session-state actor side without sending a result.
Common situations: Session-state actor stopped during shutdown while a client still issues prepare_replace; actor handler panicked mid-prepare; actor was never spawned and the message landed nowhere that replies.
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
- application actor call timed out
- clash config actor reply dropped
- clash config actor call timed out
- session state actor call timed out
- IPC server is already initialized
AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08).
Data as JSON: /api/errors/5bf5d26084fbe4c4.
Report an issue: GitHub.