{"record":{"id":"b5fbb2e70b3dab11","repo":"jdx/mise","slug":"cannot-track-through-symlinked-parent-expli","errorCode":null,"errorMessage":"cannot track {} through symlinked parent {}; explicitly track the link itself and its real target instead","messagePattern":"cannot track (.+?) through symlinked parent (.+?); explicitly track the link itself and its real target instead","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/history/tracked.rs","lineNumber":838,"sourceCode":"        Some(components.collect::<PathBuf>())\n    };\n    let Some((base, rest)) = bases\n        .iter()\n        .filter_map(|base| relative(base).map(|rest| (base, rest)))\n        .max_by_key(|(base, _)| base.components().count())\n    else {\n        eyre::bail!(\n            \"tracking requires a portable path under home or the mise configuration directory\"\n        );\n    };\n    let mut ancestor = base.clone();\n    for component in rest\n        .components()\n        .take(rest.components().count().saturating_sub(1))\n    {\n        ancestor.push(component);\n        if file::is_symlink_or_junction(&ancestor) {\n            eyre::bail!(\n                \"cannot track {} through symlinked parent {}; explicitly track the link itself and its real target instead\",\n                display_path(path),\n                display_path(&ancestor)\n            );\n        }\n    }\n    Ok(())\n}\n\n/// Turns a display or absolute path into its snapshot-tree path.\npub(crate) fn display_to_tree_path(path: &str) -> String {\n    // the link itself, never its destination: a tracked symlink is captured\n    // as a link and addressed as one\n    let expanded = normalize_target(Path::new(path));\n    let config = normalize(&global_config_dir());\n    if let Ok(relative) = expanded.strip_prefix(config) {\n        return format!(\"config/{}\", relative.to_string_lossy().replace('\\\\', \"/\"))\n            .trim_end_matches('/')","sourceCodeStart":820,"sourceCodeEnd":856,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/system/history/tracked.rs#L820-L856","documentation":"ensure_portable_ancestors walks every ancestor component of the requested path (excluding the final component) and rejects tracking if any ancestor is a symlink or junction. Watching/representing a path through a symlinked parent would capture events for the link's target under an unstable label, so mise asks you to track the link itself and its real target explicitly instead.","triggerScenarios":"Calling add_requests with a path whose intermediate directories are symlinks, e.g. tracking `~/projects/link/src/file` where `~/projects/link` is a symlink to `~/code/real`; also triggered on macOS where /tmp -> /private/tmp makes `/tmp/...` paths fail.","commonSituations":"Tracking files under symlinked project dirs (`~/dev` -> `~/Documents/dev`), under `/tmp` on macOS, or inside Dropbox/OneDrive-style symlinked folders; common on macOS where home is symlinked (/Users vs /System/Volumes/Data).","solutions":["Track the real path (resolve the symlink with `readlink -f` / `realpath`) and pass that instead.","Explicitly track the symlink itself AND its real target as separate tracking requests, as the message suggests.","Restructure your directory layout so tracked files live under real (non-symlink) directories inside home or the config dir.","On macOS, replace `/tmp/...` paths with `/private/tmp/...`."],"exampleFix":"// before\nmise history track ~/dev/tools/config.toml   # ~/dev is a symlink to ~/code\n// after\nmise history track \"$(realpath ~/dev)/tools/config.toml\"","handlingStrategy":"validation","validationCode":"// before tracking, ensure no ancestor is a symlink\nuse std::path::Path;\nfn ancestors_are_real(path: &Path) -> std::io::Result<bool> {\n    let mut is_link = false;\n    for anc in path.ancestors().skip(1) {\n        if anc.symlink_metadata().map(|m| m.file_type().is_symlink()).unwrap_or(false) {\n            is_link = true;\n            eprintln!(\"symlinked ancestor: {:?}\", anc);\n        }\n    }\n    Ok(!is_link)\n}","typeGuard":null,"tryCatchPattern":"match result {\n    Err(e) if e.to_string().contains(\"symlinked parent\") => {\n        eprintln!(\"use the real path (realpath) or track the link and target explicitly\");\n    }\n    other => other?,\n}","preventionTips":["Pass realpath-resolved paths to tracking APIs","On macOS, prefer /private/tmp over /tmp","Avoid nested symlinked directory layouts (Dropbox/iCloud redirections) for tracked roots","Track symlink roots explicitly as links rather than paths beneath them"],"tags":["history-tracking","symlink","path-validation"],"backgroundTag":"unsupported-operation","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"}