{"record":{"id":"2b3b507736085e19","repo":"libnyanpasu/clash-nyanpasu","slug":"uid-derived-path-is-always-a-valid-managed-path","errorCode":null,"errorMessage":"uid-derived path is always a valid managed path","messagePattern":"uid-derived path is always a valid managed path","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/tauri/src/state/profiles/actor.rs","lineNumber":1911,"sourceCode":"                }\n            }\n            ProfilesActorMessage::ReplaceDefinition {\n                uid,\n                definition,\n                reply,\n            } => {\n                let versioned = state.manager.snapshot_handle().load();\n                let expected_version = versioned.version;\n                let before = versioned.state.clone();\n                drop(versioned);\n                let result = match before.items.get(&uid) {\n                    None => Err(ProfilesError::ProfileNotFound(uid.clone())),\n                    Some(previous_item) => {\n                        let previous_source = previous_item.definition.source().cloned();\n                        let mut definition = definition;\n                        let ext = Self::canonical_extension(&definition);\n                        let canonical = ManagedProfilePath::new(format!(\"{uid}.{ext}\"))\n                            .expect(\"uid-derived path is always a valid managed path\");\n                        let same_slot = match (&previous_source, definition.source()) {\n                            (Some(previous), Some(next)) => {\n                                Self::retains_materialization(previous, next, &canonical)\n                            }\n                            _ => false,\n                        };\n                        if let Some(source) = definition.source_mut() {\n                            let materialized = source.materialized_mut();\n                            materialized.file = canonical.clone();\n                            materialized.updated_at = if same_slot {\n                                previous_source\n                                    .as_ref()\n                                    .and_then(|source| source.materialized().updated_at)\n                            } else {\n                                None\n                            };\n                            if let ProfileSource::Remote { subscription, .. } = source {\n                                *subscription = match (same_slot, previous_source.as_ref()) {","sourceCodeStart":1893,"sourceCodeEnd":1929,"githubUrl":"https://github.com/libnyanpasu/clash-nyanpasu/blob/f7dbce2997c633e484f54788035e770b3ee99773/backend/tauri/src/state/profiles/actor.rs#L1893-L1929","documentation":"This is a panic raised by `.expect()` when converting a uid-derived string into a `ManagedProfilePath` during profile item updates in the profiles actor. The code asserts that any path of the form `\"{uid}.{ext}\"` built from an existing profile uid and a canonical extension is always valid under the managed-path rules (non-empty, sane length, no illegal segments). If this expect fires, the uid/extension produced a path violating `ManagedProfilePath::new` invariants, which the actor treats as an unrecoverable internal bug, not a user error.","triggerScenarios":"Calling the profile-item update path on the profiles actor (around actor.rs:1911) when a previous item lookup succeeded but `canonical_extension(&definition)` returns an extension containing path-illegal characters, or the uid itself is malformed/empty so the composed string fails `ManagedProfilePath::new` validation.","commonSituations":"A profile item persisted by an older app version carries a uid or source extension that fails newer path validation; a corrupted profiles.yaml or a hand-edited profile definition whose source yields a bogus extension (e.g. contains `/`, `..`, or is empty).","solutions":["Inspect the profile item's uid and its definition source extension; delete or repair the offending profile entry in the profiles config file.","Check `ManagedProfilePath::new` validation rules and confirm `canonical_extension` sanitizes/whitelists the extension before composing the path.","Upgrade/repair the profiles data via the app's own migration rather than feeding legacy definitions into the update path.","If reproducible, file a bug with the profile definition that triggers it — this is an internal invariant violation, not a supported input state."],"exampleFix":"// before\nlet canonical = ManagedProfilePath::new(format!(\"{uid}.{ext}\"))\n    .expect(\"uid-derived path is always a valid managed path\");\n// after\nlet canonical = ManagedProfilePath::new(format!(\"{uid}.{ext}\"))\n    .map_err(|e| ProfilesError::InvalidManagedPath(uid.clone(), e.to_string()))?;","handlingStrategy":"validation","validationCode":"fn validate_uid_extension(uid: &str, ext: &str) -> Result<(), String> {\n    if uid.is_empty() || ext.is_empty() {\n        return Err(\"uid and extension must be non-empty\".into());\n    }\n    let candidate = format!(\"{uid}.{ext}\");\n    ManagedProfilePath::new(candidate).map(|_| ()).map_err(|e| e.to_string())\n}","typeGuard":"fn is_valid_managed_path(s: &str) -> bool {\n    ManagedProfilePath::new(s.to_owned()).is_ok()\n}","tryCatchPattern":"let canonical = ManagedProfilePath::new(format!(\"{uid}.{ext}\"))\n    .map_err(|e| ProfilesError::InvalidManagedPath(uid.clone(), e.to_string()))?;","preventionTips":["Sanitize/whitelist the extension in `canonical_extension` so it can never contain path-illegal characters","Validate uids when profiles are loaded/persisted, not at path-composition time","Add a test feeding legacy/corrupted profile definitions through the update path"],"tags":["rust","invariant","profiles","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"}