{"record":{"id":"e0a4b491a5753a55","repo":"jdx/mise","slug":"global-configuration-directory-must-not-be-a-symli","errorCode":null,"errorMessage":"global configuration directory must not be a symlink","messagePattern":"global configuration directory must not be a symlink","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/remote_repository.rs","lineNumber":194,"sourceCode":"        &global_directory(),\n    )\n}\n\nfn install_at(\n    bundle: &Path,\n    origin: &str,\n    revision: &str,\n    update: bool,\n    yes: bool,\n    dry_run: bool,\n    destination: &Path,\n) -> Result<PathBuf> {\n    validate_origin(origin)?;\n    if !matches!(revision.len(), 40 | 64) || !revision.bytes().all(|b| b.is_ascii_hexdigit()) {\n        bail!(\"invalid pinned revision\");\n    }\n    if destination.is_symlink() {\n        bail!(\"global configuration directory must not be a symlink\");\n    }\n    let parent = destination\n        .parent()\n        .ok_or_else(|| eyre::eyre!(\"missing parent directory\"))?;\n    if !dry_run {\n        std::fs::create_dir_all(parent)?;\n    }\n    let _lock = crate::lock_file::LockFile::new(destination).lock()?;\n    // the checkout is renamed into place, so it is staged next to the\n    // destination; a dry run never renames and leaves the parent alone\n    let temporary = if dry_run {\n        tempfile::tempdir()?\n    } else {\n        tempfile::tempdir_in(parent)?\n    };\n    let checkout = temporary.path().join(\"checkout\");\n    let shown = crate::file::display_path(destination);\n    let mut command = Command::new(\"git\");","sourceCodeStart":176,"sourceCodeEnd":212,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/system/remote_repository.rs#L176-L212","documentation":"The destination global configuration directory must be a real directory; if it is a symlink, install refuses to avoid writing configuration through a link to an unexpected location (security/integrity guard).","triggerScenarios":"Calling install_at / install / install_source / preview_source where destination.is_symlink() is true — e.g. ~/.config/mise replaced by a symlink to a dotfiles repo or synced folder.","commonSituations":"Users symlinking their config dir into a git-managed dotfiles tree, or sync tools (Dropbox etc.) that replaced the directory with a link.","solutions":["Replace the symlink with a real directory (copy contents back)","Point the tool at a non-symlink destination path","Install from the dotfiles repository itself rather than symlinking the config dir"],"exampleFix":"# before\n~/.config/mise -> ~/dotfiles/mise\n# after\n$ rm ~/.config/mise && cp -r ~/dotfiles/mise ~/.config/mise","handlingStrategy":"validation","validationCode":"fn dest_is_real_dir(p: &Path) -> bool {\n    !p.is_symlink() && p.is_dir()\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Do not symlink the global config directory into dotfiles repos; use a real dir plus a managed sync mechanism","Check `ls -la` on the config path before onboarding","If using dotfiles managers, have them manage files, not the directory itself"],"tags":["filesystem","symlink","security"],"backgroundTag":"invalid-config-value","analyzedSha":"afd2eddd3a50c16190efc1c7e94404b48f72af57","analyzedAt":"2026-09-09T01:38:25.179Z","contentChangedAt":"2026-09-09T01:38:25.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}