jdx/mise · error

global configuration directory must not be a symlink

Error message

global configuration directory must not be a symlink

What it means

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).

Source

Thrown at src/system/remote_repository.rs:194

        &global_directory(),
    )
}

fn install_at(
    bundle: &Path,
    origin: &str,
    revision: &str,
    update: bool,
    yes: bool,
    dry_run: bool,
    destination: &Path,
) -> Result<PathBuf> {
    validate_origin(origin)?;
    if !matches!(revision.len(), 40 | 64) || !revision.bytes().all(|b| b.is_ascii_hexdigit()) {
        bail!("invalid pinned revision");
    }
    if destination.is_symlink() {
        bail!("global configuration directory must not be a symlink");
    }
    let parent = destination
        .parent()
        .ok_or_else(|| eyre::eyre!("missing parent directory"))?;
    if !dry_run {
        std::fs::create_dir_all(parent)?;
    }
    let _lock = crate::lock_file::LockFile::new(destination).lock()?;
    // the checkout is renamed into place, so it is staged next to the
    // destination; a dry run never renames and leaves the parent alone
    let temporary = if dry_run {
        tempfile::tempdir()?
    } else {
        tempfile::tempdir_in(parent)?
    };
    let checkout = temporary.path().join("checkout");
    let shown = crate::file::display_path(destination);
    let mut command = Command::new("git");

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Replace the symlink with a real directory (copy contents back)
  2. Point the tool at a non-symlink destination path
  3. Install from the dotfiles repository itself rather than symlinking the config dir

Example fix

# before
~/.config/mise -> ~/dotfiles/mise
# after
$ rm ~/.config/mise && cp -r ~/dotfiles/mise ~/.config/mise
Defensive patterns

Strategy: validation

Validate before calling

fn dest_is_real_dir(p: &Path) -> bool {
    !p.is_symlink() && p.is_dir()
}

Prevention

When it happens

Trigger: 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.

Common situations: Users symlinking their config dir into a git-managed dotfiles tree, or sync tools (Dropbox etc.) that replaced the directory with a link.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of jdx/mise@afd2eddd3a (2026-09-09). Data as JSON: /api/errors/e0a4b491a5753a55. Report an issue: GitHub.