jdx/mise · error

defaults patch path must not be empty

Error message

defaults patch path must not be empty

What it means

`patch_value` requires at least one path component because it must know which dictionary key to insert. It also assumes the current node is a dictionary; an empty path (or a non-dictionary current node, which yields the sibling 'expected dictionary' error) cannot be patched.

Solutions

  1. Provide a non-empty path ending in the key to set (e.g. ["key"])
  2. Fix the patch configuration so the final key name is present
  3. Check the recursion/caller logic so the last component is not stripped before patch_value

Example fix

// before
patch_value(&mut root, &[], value)?; // empty path
// after
patch_value(&mut root, &["SomeKey".to_string()], value)?;
Defensive patterns

Strategy: validation

Validate before calling

if path.is_empty() {
    return Err("defaults patch path must not be empty".into());
}

Try / catch

match patch_value(&mut root, &path, value) {
    Err(e) if e.to_string().contains("path must not be empty") => eprintln!("patch request missing key path"),
    r => r?,
}

Prevention

When it happens

Trigger: Calling patch_value with an empty path slice — e.g. a patch request with no key components — typically when the final path segment was consumed by recursion or the config omitted a key.

Common situations: Malformed defaults patch definitions missing the final key, or recursive patching logic splitting the path down to zero components.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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

Appendix: source

Thrown at src/system/defaults.rs:341

    mut current: Option<&'a plist::Value>,
    request: &DefaultsRequest,
) -> Result<Option<&'a plist::Value>> {
    if let Some(path) = &request.path {
        for component in path {
            current = match current {
                Some(plist::Value::Dictionary(dict)) => dict.get(component),
                None => return Ok(None),
                Some(_) => eyre::bail!("expected dictionary along defaults patch path: {request}"),
            };
        }
    }
    Ok(current)
}

#[cfg(any(target_os = "macos", test))]
fn patch_value(current: &mut plist::Value, path: &[String], value: plist::Value) -> Result<()> {
    let Some((key, rest)) = path.split_first() else {
        eyre::bail!("defaults patch path must not be empty");
    };
    let dict = current
        .as_dictionary_mut()
        .ok_or_else(|| eyre::eyre!("expected dictionary along defaults patch path"))?;
    if rest.is_empty() {
        dict.insert(key.clone(), value);
    } else {
        if !dict.contains_key(key) {
            dict.insert(
                key.clone(),
                plist::Value::Dictionary(plist::Dictionary::new()),
            );
        }
        patch_value(
            dict.get_mut(key).expect("dictionary entry inserted"),
            rest,
            value,
        )?;

View on GitHub (pinned to 533346cc37)