{"record":{"id":"6a53d5d5caf57d7c","repo":"libnyanpasu/clash-nyanpasu","slug":"replacement-target-remains-in-the-candidate-snapsh","errorCode":null,"errorMessage":"replacement target remains in the candidate snapshot","messagePattern":"replacement target remains in the candidate snapshot","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/tauri/src/state/profiles/actor.rs","lineNumber":1961,"sourceCode":"                            (definition.source().is_none() || old_path != canonical)\n                                .then_some(old_path)\n                        });\n                        // A changed remote definition receives a durable empty\n                        // placeholder instead of retaining stale bytes. Its next\n                        // refresh replaces it through the file-first protocol.\n                        let resource = if same_slot {\n                            Ok(None)\n                        } else {\n                            Self::resource_for_definition(state, &definition, None).await\n                        };\n                        match resource {\n                            Err(error) => Err(error),\n                            Ok(resource) => {\n                                let mut next = before.clone();\n                                let item = next\n                                    .items\n                                    .get_mut(&uid)\n                                    .expect(\"replacement target remains in the candidate snapshot\");\n                                item.set_definition(definition);\n                                Self::commit_state_first(\n                                    &myself,\n                                    state,\n                                    expected_version,\n                                    before,\n                                    next,\n                                    AffectsRule::Touched(uid),\n                                    resource.map(|resource| (canonical, resource)),\n                                    cleanup_path,\n                                    None,\n                                )\n                                .await\n                            }\n                        }\n                    }\n                };\n                let _ = reply.send(result);","sourceCodeStart":1943,"sourceCodeEnd":1979,"githubUrl":"https://github.com/libnyanpasu/clash-nyanpasu/blob/f7dbce2997c633e484f54788035e770b3ee99773/backend/tauri/src/state/profiles/actor.rs#L1943-L1979","documentation":"This panic fires in the profiles actor when committing a profile-item replacement: the code clones the pre-commit snapshot `before`, then expects the just-resolved uid to still be present in `next.items`. The invariant is that the same snapshot in which the uid was looked up and validated is the one being mutated, so the key must exist. If it is missing, the candidate snapshot was mutated or swapped concurrently — a bug in actor state handling, not a caller mistake.","triggerScenarios":"Invoking the profile item update/replacement command on the profiles actor (actor.rs:1961) where the uid was found in `state`/`before` but the cloned `next` snapshot no longer contains the key — e.g. snapshot lifecycle broken between validation and mutation.","commonSituations":"Developers refactoring the actor's commit path (e.g. rebasing snapshot clones, reordering `commit_state_first`) and accidentally mutating a different snapshot than the one holding the target item; corrupted in-memory state after a failed partial commit.","solutions":["Verify the `before` snapshot passed to this code is the same snapshot the uid lookup ran against; do not re-clone from a later state.","Ensure the actor handles one commit at a time (ractor serialization) and no other code mutates `next.items` between the `get` and `get_mut` calls.","Add a debug assert/log right before `get_mut` to dump the snapshot keys when this fires.","If you cannot reach this code path legitimately, treat any occurrence as an actor bug and report it with the request that triggered it."],"exampleFix":"// before\nlet item = next\n    .items\n    .get_mut(&uid)\n    .expect(\"replacement target remains in the candidate snapshot\");\n// after\nlet item = next.items.get_mut(&uid).ok_or_else(|| {\n    ProfilesError::ProfileNotFound(uid.clone())\n})?;","handlingStrategy":"type-guard","validationCode":"debug_assert!(\n    next.items.contains_key(&uid),\n    \"uid {uid} missing from candidate snapshot before mutation\"\n);","typeGuard":"fn target_in_snapshot(snapshot: &ProfilesSnapshot, uid: &Uid) -> bool {\n    snapshot.items.contains_key(uid)\n}","tryCatchPattern":"let Some(item) = next.items.get_mut(&uid) else {\n    return Err(ProfilesError::ProfileNotFound(uid.clone()));\n};","preventionTips":["Always mutate the exact snapshot that was validated; never re-clone from a later state","Keep uid lookup and mutation adjacent with no intervening snapshot swaps","Cover the commit path with an actor test that replaces an existing item"],"tags":["rust","invariant","actor","panic"],"backgroundTag":"internal-invariant-violation","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"}