libnyanpasu/clash-nyanpasu · error
effect function failed: {error:#?}
Error message
effect function failed: {error:#?} What it means
In with_pending_state_if_version, the conditional effect callback returned an error after the state transaction was prepared. The coordinator rolls back the pending state (RollbackReason::CoordinatorError) and surfaces ConditionalEffectError::Effect so the state remains consistent.
Source
Thrown at backend/nyanpasu-core/src/state/coordinator.rs:262
};
let tx = new_transaction(
change,
self.current_state.clone(),
subscribers,
notify_strategy,
permit,
);
let tx = match tx.prepare().await {
Ok((_report, prepared_tx)) => prepared_tx,
Err(err) => {
let (report, _) = *err;
return Err(ConditionalEffectError::State(
StateChangedError::PrepareAck(PrepareAckError { report }),
));
}
};
if let Err(error) = effect_fn(new_state).await {
tx.rollback(RollbackReason::CoordinatorError(Arc::new(anyhow!(
"effect function failed: {error:#?}"
))))
.await;
return Err(ConditionalEffectError::Effect(error));
}
match tx.try_commit() {
Ok(committed_tx) => {
committed_tx.notify_committed().await;
self.mark_change_id_committed(next_changed_id);
Ok(())
}
Err(commit_mismatch) => {
let actual = self.sync_change_id_after_cas_mismatch();
let commit_error = StateChangedError::StateCasMismatch {
expected: current_state.version,
actual,
};
let committed = self.snapshot_versioned();View on GitHub (pinned to f7dbce2997)
Solutions
- Inspect the nested error ({error:#?}) — the root cause is inside the effect function, not the coordinator.
- Make the effect idempotent and retry the whole with_pending_state_if_version call after the cause is fixed.
- Return a typed, narrow error from effect_fn to make diagnostics actionable.
- If the effect is best-effort, catch its error inside effect_fn and log instead of failing the transaction.
Example fix
// before
.with_pending_state_if_version(ver, |state| async {
write_runtime_config(state)?;
Ok(())
})
// after
.with_pending_state_if_version(ver, |state| async {
match write_runtime_config(state).await {
Ok(()) => Ok(()),
Err(e) => {
log::warn!("best-effort config write failed: {e}");
Ok(()) // don't roll back state for best-effort effects
}
}
}) Defensive patterns
Strategy: retry
Validate before calling
// validate effect preconditions before entering the transaction
async fn effect_safe(state: &State) -> bool {
state.runtime_config_path.is_writable() && core_reachable().await
} Try / catch
match coordinator.with_pending_state_if_version(ver, effect_fn).await {
Err(ConditionalEffectError::Effect(e)) => {
log::error!("effect failed, state rolled back: {e:#}");
// safe to retry after fixing the cause
}
Err(e) => return Err(e.into()),
Ok(v) => v,
} Prevention
- Make effect functions idempotent so retries are safe
- Keep side effects fast and non-blocking inside transactions
- Log the inner error chain ({error:#?}) to find the real failure
- Return best-effort errors as warnings instead of failing the transaction
When it happens
Trigger: Calling with_pending_state_if_version with an effect_fn that fails (I/O, config write, validation) while a state version transition was pending — e.g. a Tauri-side commit whose post-commit effect could not complete.
Common situations: Effect functions writing runtime config or notifying the core hitting transient failures (core restarting, file locked), or bugs/panics in effect logic.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- effect function failed: {e:#?}
- compensation fenced by diverged target at {}
- state transaction dropped before commit or rollback complete
- IPC server is already initialized
- cannot cancel an activated cleanup operation
AI-assisted analysis of libnyanpasu/clash-nyanpasu@f7dbce2997 (2026-09-08).
Data as JSON: /api/errors/ae0b344ae7dd5902.
Report an issue: GitHub.