jdx/mise · error

expected StartCalendarInterval dictionary, got {value:?}

Error message

expected StartCalendarInterval dictionary, got {value:?}

What it means

A companion panic in `test_render_plist`: after confirming the plist root is a dictionary, the test looks up the `StartCalendarInterval` key and asserts it is a `Value::Dictionary` containing Hour=2 and Minute=0. If the key is missing or holds another value type, the catch-all panics with 'expected StartCalendarInterval dictionary, got {value:?}'.

Source

Thrown at src/system/launchd.rs:770

            Some(&Value::Integer(300.into()))
        );
        assert_eq!(
            dict.get("QueueDirectories"),
            Some(&Value::Array(vec![Value::String(
                crate::dirs::HOME
                    .join("Library")
                    .join("Queues")
                    .join("sync")
                    .to_string_lossy()
                    .to_string()
            )]))
        );
        match dict.get("StartCalendarInterval") {
            Some(Value::Dictionary(interval)) => {
                assert_eq!(interval.get("Hour"), Some(&Value::Integer(2.into())));
                assert_eq!(interval.get("Minute"), Some(&Value::Integer(0.into())));
            }
            value => panic!("expected StartCalendarInterval dictionary, got {value:?}"),
        }
        assert_eq!(
            dict.get("WorkingDirectory"),
            Some(&Value::String(
                crate::dirs::HOME.to_string_lossy().to_string()
            ))
        );
        // joined component-by-component to match how `file::replace_path`
        // expands the `~/...` request values: the rendered string uses the
        // platform separator throughout, so `join("Library/Logs/sync.log")`
        // would keep forward slashes and mismatch on Windows
        assert_eq!(
            dict.get("StandardOutPath"),
            Some(&Value::String(
                crate::dirs::HOME
                    .join("Library")
                    .join("Logs")
                    .join("sync.log")

View on GitHub (pinned to afd2eddd3a)

Solutions

  1. Verify `render_plist` emits `StartCalendarInterval` as a nested `<dict>` whenever the request has a schedule.
  2. Check that Hour/Minute values from the request are mapped into the nested dictionary, not top-level keys.
  3. Print the whole parsed dictionary to see which keys were actually emitted.
  4. Ensure optional schedule fields with defaults (e.g. Minute=0) are not treated as 'unset' and skipped.

Example fix

// before
value => panic!("expected StartCalendarInterval dictionary, got {value:?}"),
// after
value => panic!("expected StartCalendarInterval dictionary, got {value:?}; keys: {:?}", dict.keys().collect::<Vec<_>>()),
Defensive patterns

Strategy: type-guard

Validate before calling

// rust
if !dict.contains_key("StartCalendarInterval") {
    return Err("plist missing StartCalendarInterval".into());
}

Type guard

fn calendar_interval(dict: &Dictionary) -> Option<&Dictionary> {
    match dict.get("StartCalendarInterval") {
        Some(Value::Dictionary(d)) => Some(d),
        _ => None,
    }
}

Try / catch

// rust
match dict.get("StartCalendarInterval") {
    Some(Value::Dictionary(interval)) => /* assert Hour/Minute */,
    other => return Err(anyhow!("expected StartCalendarInterval dictionary, got {other:?}")),
}

Prevention

When it happens

Trigger: `render_plist` emitting no `StartCalendarInterval` key (None case) or emitting it as a non-dictionary (e.g. string) for a request with schedule settings — a mapping regression between the request's schedule fields and the plist output.

Common situations: Schedule fields dropped from the request struct or renderer; `Hour`/`Minute` written at the top level instead of nested; key renamed in the plist output; optional schedule omitted when a field defaults.

Related errors


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