jdx/mise · error

cannot relocate : not enough padding to grow load commands…

Error message

cannot relocate {}: not enough padding to grow load commands ({} > {} bytes)

What it means

Thrown when the rewritten load-command table grows larger than the padding available before the first section data byte. Mach-O patching can only grow commands into padding between sizeofcmds and the first section — there is no room to shift section data, so relocation is refused rather than corrupting the binary.

Solutions

  1. Choose a target prefix that is the same length or shorter than the original path.
  2. Pad the original install names at build time (e.g. via `-headerpad_max_install_names` ld flag) so future rewrites fit.
  3. Rebuild the formula/binary from source with the desired paths instead of binary-patching.
  4. Fall back to symlinking the original path structure so no rewrite is needed.

Example fix

// before: patch to a longer prefix, fails
patch(&path, &[("/opt/homebrew/opt", "/very/long/custom/prefix/opt")])?;
// after: keep length compatible or rebuild
let new_prefix = "/short/pfx";
assert!(new_prefix.len() <= "/opt/homebrew".len());
patch(&path, &[("/opt/homebrew", new_prefix)])?;
Defensive patterns

Strategy: validation

Validate before calling

fn rewrite_fits(old_prefix: &str, new_prefix: &str) -> bool {
    new_prefix.len() <= old_prefix.len()
}

Try / catch

match patch(&path, &replacements) {
    Err(e) if e.to_string().contains("not enough padding") => {
        eprintln!("prefix too long for binary patching; rebuilding from source");
        // fall back to `brew install --build-from-source`
    }
    other => other?,
}

Prevention

When it happens

Trigger: After rebuilding commands with expanded path strings (e.g. longer install names/prefixes), HEADER_SIZE_64 + new_sizeofcmds > first_data triggers the bail. Called by patch when path replacements lengthen LC_LOAD_DYLIB/LC_ID_DYLIB/LC_RPATH strings beyond available padding.

Common situations: Relocating brew bottles to a prefix longer than the original; replacing short paths with much longer custom paths; binaries with little or no inter-table padding; many dylib entries each growing slightly.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17). Data as JSON: /api/errors/324d9246646eed28. Report an issue: GitHub.

Appendix: source

Thrown at src/system/packages/brew/macho.rs:152

                    let new_cmdsize = (str_off + new.len() + 1).div_ceil(8) * 8;
                    let mut rebuilt = bytes[..str_off].to_vec();
                    rebuilt.extend_from_slice(&new);
                    rebuilt.resize(new_cmdsize.max(cmdsize), 0);
                    let len = rebuilt.len() as u32;
                    rebuilt[4..8].copy_from_slice(&len.to_le_bytes());
                    bytes = rebuilt;
                    changed = true;
                }
            }
        }
        commands.push(bytes);
    }
    if !changed {
        return Ok(false);
    }
    let new_sizeofcmds: usize = commands.iter().map(|c| c.len()).sum();
    if HEADER_SIZE_64 + new_sizeofcmds > first_data {
        bail!(
            "cannot relocate {}: not enough padding to grow load commands ({} > {} bytes)",
            path.display(),
            HEADER_SIZE_64 + new_sizeofcmds,
            first_data,
        );
    }
    let mut out = Vec::with_capacity(new_sizeofcmds);
    for c in &commands {
        out.extend_from_slice(c);
    }
    // zero everything from the header to the first data byte, then lay the
    // table down — leaves old table bytes cleanly erased
    slice[HEADER_SIZE_64..first_data].fill(0);
    slice[HEADER_SIZE_64..HEADER_SIZE_64 + out.len()].copy_from_slice(&out);
    slice[20..24].copy_from_slice(&(new_sizeofcmds as u32).to_le_bytes());
    Ok(true)
}

View on GitHub (pinned to 533346cc37)