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

  1. Confirm the SessionStateActor is running (not stopped) before issuing prepare_replace.
  2. Retry the operation; if it follows an actor stop, recreate the client/actor pair.
  3. Check actor handler logs for panics or early returns that skip the reply.
  4. 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

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


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