libnyanpasu/clash-nyanpasu · error
clash config actor reply dropped
Error message
clash config actor reply dropped
What it means
This error is raised by `ClashConfigClient::prepare_replace` when the underlying ractor `actor_ref.call(...)` returns `CallResult::SenderError`. That means the RPC reply port was dropped before the clash-config actor sent a response — typically because the actor was stopped, crashed, or its message handler exited without calling the reply port. The client cannot return a `PreparedTypedReplace<ClashConfig>`, so it bails with this message.
Source
Thrown at backend/tauri/src/client/clash_config.rs:126
self.replace_prepared_if_version(expected_version, prepared)
.await
}
pub(crate) async fn prepare_replace(
&self,
state: ClashConfig,
) -> anyhow::Result<PreparedTypedReplace<ClashConfig>> {
match self
.inner
.actor_ref
.call(
|reply| ClashConfigActorMessage::PrepareReplace { state, reply },
None,
)
.await?
{
CallResult::Success(result) => result,
CallResult::SenderError => anyhow::bail!("clash config actor reply dropped"),
CallResult::Timeout => anyhow::bail!("clash config actor call timed out"),
}
}
pub(crate) async fn replace_prepared_if_version(
&self,
expected_version: u64,
prepared: PreparedTypedReplace<ClashConfig>,
) -> anyhow::Result<ConditionalReplaceResult<ClashConfigSnapshot>> {
match self
.inner
.actor_ref
.call(
|reply| ClashConfigActorMessage::ReplacePreparedIfVersion {
expected_version,
prepared,
reply,
},View on GitHub (pinned to f7dbce2997)
Solutions
- Check that the ClashConfigActor is still alive before the call (or that the ClashConfigClient was not dropped, since Drop stops the actor).
- Inspect the actor's handler for ClashConfigActorMessage::PrepareReplace and ensure every code path — including error/early-return paths — sends on the RpcReplyPort.
- Add panic handling / supervision around the actor so a panic in the legacy bridge or persistence layer doesn't silently kill the actor mid-RPC.
- Retry the operation by rebuilding the client via `ClashConfigClient::new` if the actor was stopped during shutdown.
Example fix
// before: handler drops reply on error
ClashConfigActorMessage::PrepareReplace { state, reply } => {
let prepared = state.with_bridge(&bridge)?; // early `?` drops `reply`
reply.send(prepared)?;
}
// after: always reply
ClashConfigActorMessage::PrepareReplace { state, reply } => {
let _ = reply.send(state.with_bridge(&bridge).map_err(|e| anyhow::anyhow!(e.to_string())));
} Defensive patterns
Strategy: try-catch
Validate before calling
// Rust: no pre-call validation is possible; guard the call site instead
if client_is_shutting_down() {
anyhow::bail!("skipping prepare_replace: actor is being torn down");
} Try / catch
match client.replace_if_version(expected_version, next).await {
Ok(ConditionalReplaceResult::Replaced(s)) => { /* committed */ }
Ok(ConditionalReplaceResult::Conflict { actual_version }) => { /* re-read and retry */ }
Err(e) if e.to_string().contains("reply dropped") => {
// actor stopped or panicked: rebuild the client, then retry once
let client = rebuild_client().await?;
}
Err(e) => return Err(e),
} Prevention
- Never drop the ClashConfigClient while a replace_if_version call is in flight.
- Ensure every actor handler branch sends on the RpcReplyPort, including error paths.
- Add supervision/panic hooks around the actor so panics surface in logs, not silent SenderError.
- Keep the legacy bridge `prepare()` cheap and infallible, or reply with an Err instead of dropping the port.
When it happens
Trigger: Calling `replace_if_version` (which internally calls `prepare_replace`) while the ClashConfigActor is stopped or dead — e.g. after `ClashConfigClientInner::drop` invoked `actor_ref.stop(None)`, after the actor panicked handling an earlier message, or if the actor's PrepareReplace handler returned without replying (e.g. legacy bridge `prepare()` failed and the reply port was dropped along the error path).
Common situations: App shutdown racing a config replace; a panic in the actor's persistent-state manager or legacy bridge killing the actor; sending a replace through a client clone whose actor was already torn down; an actor handler bug that forgets `reply.send(...)` on an error branch.
Related errors
- clash config actor call timed out
- application actor call timed out
- session state actor reply dropped
- session state actor call timed out
- application actor reply dropped
AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08).
Data as JSON: /api/errors/f8822c532f7cc208.
Report an issue: GitHub.