{"record":{"id":"85e0913ed5d935ee","repo":"jdx/mise","slug":"a-validated-copy-link-has-a-staged-parent","errorCode":null,"errorMessage":"a validated copy_link has a staged parent","messagePattern":"a validated copy_link has a staged parent","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/remote.rs","lineNumber":881,"sourceCode":"    let path = source.join(link);\n    let metadata = fs::symlink_metadata(&path)\n        .wrap_err_with(|| format!(\"copy_link does not exist: {}\", link.display()))?;\n    if !metadata.file_type().is_symlink() {\n        bail!(\"copy_link is not a symbolic link: {}\", link.display());\n    }\n    Ok(())\n}\n\nfn materialize_link(source: &Path, staged_source: &Path, link: &Path) -> Result<()> {\n    validate_copy_link_path(staged_source, link)\n        .wrap_err_with(|| format!(\"staged copy_link is unsafe: {}\", link.display()))?;\n    let source_link = source.join(link);\n    let staged_link = staged_source.join(link);\n    let target = fs::canonicalize(&source_link)\n        .wrap_err_with(|| format!(\"failed to resolve copy_link {}\", link.display()))?;\n    let parent = staged_link\n        .parent()\n        .expect(\"a validated copy_link has a staged parent\");\n    let original_permissions = make_directory_writable(parent)?;\n    let result = (|| {\n        crate::file::remove_file(&staged_link)?;\n        if target.is_dir() {\n            fs::create_dir(&staged_link)?;\n            crate::file::copy_dir_all_preserve_symlinks(&target, &staged_link)?;\n        } else {\n            crate::file::copy(&target, &staged_link)?;\n        }\n        Ok(())\n    })();\n    if let Some(permissions) = original_permissions {\n        fs::set_permissions(parent, permissions).wrap_err_with(|| {\n            format!(\n                \"failed to restore staged directory permissions: {}\",\n                parent.display()\n            )\n        })?;","sourceCodeStart":863,"sourceCodeEnd":899,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/system/remote.rs#L863-L899","documentation":"When materializing a `copy_link`, the code takes `staged_link.parent().expect(\"a validated copy_link has a staged parent\")`. Because `staged_link` is built as a joined path, parent() should always exist; None would mean `link` itself is an empty path or the staged root is empty, i.e. the validated link value degenerated.","triggerScenarios":"Applying a copy_link (src/system/remote.rs:881) where the `link` relative path is empty or the staged_source base is empty, making `staged_link` a bare root with no parent.","commonSituations":"A copy_link config entry with an empty or whitespace path; templated link path expanding to empty; base path left unset when joining.","solutions":["Ensure the copy_link value is a non-empty relative path (e.g. \"subdir/target\" not \"\")","Check the staged_source base is a valid non-empty directory before staging","Replace the expect with an error carrying the link value for easier diagnosis"],"exampleFix":"// before\ncopy_link = \"\"\n// after\ncopy_link = \"bin/shared-tool\"","handlingStrategy":"validation","validationCode":"if link.as_os_str().is_empty() {\n    return Err(anyhow!(\"copy_link path must be non-empty\"));\n}","typeGuard":"fn is_valid_link(link: &Path) -> bool {\n    !link.as_os_str().is_empty() && link.is_relative()\n}","tryCatchPattern":null,"preventionTips":["Validate copy_link entries when parsing remote config","Reject empty or whitespace-only link paths early","Ensure the staged base directory is non-empty and exists"],"tags":["panic","path","remote","symlink"],"backgroundTag":"internal-invariant-violation","analyzedSha":"533346cc374382b41ec5ff70536252b2e96e725c","analyzedAt":"2026-09-17T13:35:38.149Z","contentChangedAt":"2026-09-17T13:35:38.149Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}