jdx/mise · error
expected dictionary along defaults patch path
Error message
expected dictionary along defaults patch path: {request} What it means
When evaluating a macOS `defaults` patch, the code walks a plist path expecting every intermediate node to be a dictionary. If a component along the path resolves to a non-dictionary value (array, string, date, etc.), it cannot descend further and this error is raised, naming the full request.
Solutions
- Inspect the actual plist (`defaults read <domain>`) and correct the path to only traverse dictionaries
- Delete/reset the offending key so patch_value can create a dictionary at that position
- Update the patch configuration to match the current plist structure
Example fix
// before: path = ["Window", "Frame"] where 'Window' is a string // after: path = ["NSWindow Frame"]["Main"] — fix path or reset key: // defaults delete MyApp Window
Defensive patterns
Strategy: type-guard
Validate before calling
fn path_is_dict_traversable(root: &plist::Value, path: &[String]) -> bool {
let mut cur = Some(root);
for key in path {
match cur {
Some(plist::Value::Dictionary(d)) => cur = d.get(key),
_ => return false,
}
}
true
} Type guard
fn as_dictionary(v: Option<&plist::Value>) -> Option<&plist::Dictionary> {
match v { Some(plist::Value::Dictionary(d)) => Some(d), _ => None }
} Try / catch
match selected_value(&request) {
Err(e) if e.to_string().starts_with("expected dictionary along defaults patch path") => {
eprintln!("plist shape mismatch for {}: reading live defaults", request);
}
r => r?,
} Prevention
- Read the live domain with `defaults read` and shape the path against reality
- Only traverse keys known to hold dictionaries
- Re-validate paths after macOS version upgrades
When it happens
Trigger: A defaults patch whose `path` traverses a key that exists but holds a scalar or array instead of a nested dictionary — e.g. path ["a","b"] where plist['a'] is a string.
Common situations: Domain plist shape changed between macOS versions, user hand-edited the plist, or the patch path was written against a different schema than the actual domain.
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
Related errors
- defaults patch path must not be empty
- com.apple.dock persistent-apps is not an array; left…
- `defaults ` failed
- `defaults ` failed
- defaults patch path must not be empty
AI-assisted analysis of jdx/mise@533346cc37 (2026-09-17).
Data as JSON: /api/errors/6a722420d4b8656b.
Report an issue: GitHub.
Appendix: source
Thrown at src/system/defaults.rs:331
eyre::ensure!(
!overlaps,
"overlapping macOS defaults declarations: {other}; {request}"
);
}
}
Ok(())
}
fn selected_value<'a>(
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) {View on GitHub (pinned to 533346cc37)