jdx/mise · error · eyre::Report

cannot relocate {}: replacement for {} does not fit ({} > {}

Error message

cannot relocate {}: replacement for {} does not fit ({} > {} bytes)

What it means

Bottle binaries embed fixed-size placeholder paths (the build machine's prefix, e.g. /usr/local or /opt/homebrew) that mise rewrites in place during relocation, because Mach-O/ELF offsets cannot grow. A replacement must fit into the original slot — the placeholder string plus its trailing run of NULs, minus one NUL kept as terminator. If the real prefix path is longer than the slot, in-place patching is impossible and relocation aborts with the byte counts.

Source

Thrown at src/system/packages/brew/relocate.rs:176

            // keep); find the end at the next NUL
            let str_end = content[start..]
                .iter()
                .position(|&b| b == 0)
                .map(|p| start + p)
                .unwrap_or(content.len());
            // available slot: the string plus the run of NULs after it,
            // minus one NUL that must remain as terminator
            let slot_end = content[str_end..]
                .iter()
                .position(|&b| b != 0)
                .map(|p| str_end + p)
                .unwrap_or(content.len());
            let old = content[start..str_end].to_vec();
            let mut new = r.value.clone();
            new.extend_from_slice(&old[r.placeholder.len()..]);
            let slot = slot_end.saturating_sub(start);
            if new.len() + 1 > slot {
                bail!(
                    "cannot relocate {}: replacement for {} does not fit ({} > {} bytes)",
                    path.display(),
                    String::from_utf8_lossy(r.placeholder),
                    new.len() + 1,
                    slot,
                );
            }
            content[start..start + new.len()].copy_from_slice(&new);
            for b in &mut content[start + new.len()..slot_end] {
                *b = 0;
            }
            changed = true;
            search_from = start + new.len();
        }
    }
    Ok(changed)
}

View on GitHub (pinned to 9dcfcaa0dc)

Solutions

  1. Use the default (shorter) mise install prefix instead of a deeply nested custom one
  2. Shorten the path: shallower HOME, shorter username, or a symlinked prefix such as /opt/mise pointing at the real location
  3. Install that package with native Homebrew (its real paths match the placeholders) or via another backend
Defensive patterns

Strategy: validation

Validate before calling

# estimate before installing: placeholder slots are sized for short prefixes like /usr/local
PREFIX="$(mise data-dir)/brew/..."   # the real target path baked into binaries
echo "target prefix length: ${#PREFIX} bytes (placeholders are ~10-15 bytes); keep it short or symlink /opt/mise -> $PREFIX"

Prevention

When it happens

Trigger: Relocating a file where the placeholder must be replaced by a much longer real path: the check new.len() + 1 > slot fails. Long usernames, deeply nested HOME/XDG dirs, or a custom mise data dir push the target path past the placeholder's slot size.

Common situations: Installing into /home/very-long-username/.local/share/mise/... where the path exceeds the placeholder by dozens of bytes; organizations redirecting HOME or XDG_DATA_HOME to long canonical paths in automount environments.

Related errors


AI-assisted analysis of jdx/mise@9dcfcaa0dc (2026-08-17). Data as JSON: /api/errors/a311b9256c630d41. Report an issue: GitHub.