{"record":{"id":"ae0b344ae7dd5902","repo":"libnyanpasu/clash-nyanpasu","slug":"effect-function-failed-error","errorCode":null,"errorMessage":"effect function failed: {error:#?}","messagePattern":"effect function failed: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/nyanpasu-core/src/state/coordinator.rs","lineNumber":262,"sourceCode":"        };\n        let tx = new_transaction(\n            change,\n            self.current_state.clone(),\n            subscribers,\n            notify_strategy,\n            permit,\n        );\n        let tx = match tx.prepare().await {\n            Ok((_report, prepared_tx)) => prepared_tx,\n            Err(err) => {\n                let (report, _) = *err;\n                return Err(ConditionalEffectError::State(\n                    StateChangedError::PrepareAck(PrepareAckError { report }),\n                ));\n            }\n        };\n        if let Err(error) = effect_fn(new_state).await {\n            tx.rollback(RollbackReason::CoordinatorError(Arc::new(anyhow!(\n                \"effect function failed: {error:#?}\"\n            ))))\n            .await;\n            return Err(ConditionalEffectError::Effect(error));\n        }\n        match tx.try_commit() {\n            Ok(committed_tx) => {\n                committed_tx.notify_committed().await;\n                self.mark_change_id_committed(next_changed_id);\n                Ok(())\n            }\n            Err(commit_mismatch) => {\n                let actual = self.sync_change_id_after_cas_mismatch();\n                let commit_error = StateChangedError::StateCasMismatch {\n                    expected: current_state.version,\n                    actual,\n                };\n                let committed = self.snapshot_versioned();","sourceCodeStart":244,"sourceCodeEnd":280,"githubUrl":"https://github.com/libnyanpasu/clash-nyanpasu/blob/f7dbce2997c633e484f54788035e770b3ee99773/backend/nyanpasu-core/src/state/coordinator.rs#L244-L280","documentation":"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.","triggerScenarios":"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.","commonSituations":"Effect functions writing runtime config or notifying the core hitting transient failures (core restarting, file locked), or bugs/panics in effect logic.","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."],"exampleFix":"// before\n.with_pending_state_if_version(ver, |state| async {\n    write_runtime_config(state)?;\n    Ok(())\n})\n// after\n.with_pending_state_if_version(ver, |state| async {\n    match write_runtime_config(state).await {\n        Ok(()) => Ok(()),\n        Err(e) => {\n            log::warn!(\"best-effort config write failed: {e}\");\n            Ok(()) // don't roll back state for best-effort effects\n        }\n    }\n})","handlingStrategy":"retry","validationCode":"// validate effect preconditions before entering the transaction\nasync fn effect_safe(state: &State) -> bool {\n    state.runtime_config_path.is_writable() && core_reachable().await\n}","typeGuard":null,"tryCatchPattern":"match coordinator.with_pending_state_if_version(ver, effect_fn).await {\n    Err(ConditionalEffectError::Effect(e)) => {\n        log::error!(\"effect failed, state rolled back: {e:#}\");\n        // safe to retry after fixing the cause\n    }\n    Err(e) => return Err(e.into()),\n    Ok(v) => v,\n}","preventionTips":["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"],"tags":["state","transaction","rollback","effect"],"backgroundTag":"invalid-state-transition","analyzedSha":"f7dbce2997c633e484f54788035e770b3ee99773","analyzedAt":"2026-09-08T01:24:59.197Z","contentChangedAt":"2026-09-08T01:24:59.197Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}