{"record":{"id":"935b7827f091d776","repo":"jdx/mise","slug":"expected-startcalendarinterval-dictionary-got-va","errorCode":null,"errorMessage":"expected StartCalendarInterval dictionary, got {value:?}","messagePattern":"expected StartCalendarInterval dictionary, got (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/launchd.rs","lineNumber":770,"sourceCode":"            Some(&Value::Integer(300.into()))\n        );\n        assert_eq!(\n            dict.get(\"QueueDirectories\"),\n            Some(&Value::Array(vec![Value::String(\n                crate::dirs::HOME\n                    .join(\"Library\")\n                    .join(\"Queues\")\n                    .join(\"sync\")\n                    .to_string_lossy()\n                    .to_string()\n            )]))\n        );\n        match dict.get(\"StartCalendarInterval\") {\n            Some(Value::Dictionary(interval)) => {\n                assert_eq!(interval.get(\"Hour\"), Some(&Value::Integer(2.into())));\n                assert_eq!(interval.get(\"Minute\"), Some(&Value::Integer(0.into())));\n            }\n            value => panic!(\"expected StartCalendarInterval dictionary, got {value:?}\"),\n        }\n        assert_eq!(\n            dict.get(\"WorkingDirectory\"),\n            Some(&Value::String(\n                crate::dirs::HOME.to_string_lossy().to_string()\n            ))\n        );\n        // joined component-by-component to match how `file::replace_path`\n        // expands the `~/...` request values: the rendered string uses the\n        // platform separator throughout, so `join(\"Library/Logs/sync.log\")`\n        // would keep forward slashes and mismatch on Windows\n        assert_eq!(\n            dict.get(\"StandardOutPath\"),\n            Some(&Value::String(\n                crate::dirs::HOME\n                    .join(\"Library\")\n                    .join(\"Logs\")\n                    .join(\"sync.log\")","sourceCodeStart":752,"sourceCodeEnd":788,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/system/launchd.rs#L752-L788","documentation":"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:?}'.","triggerScenarios":"`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.","commonSituations":"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.","solutions":["Verify `render_plist` emits `StartCalendarInterval` as a nested `<dict>` whenever the request has a schedule.","Check that Hour/Minute values from the request are mapped into the nested dictionary, not top-level keys.","Print the whole parsed dictionary to see which keys were actually emitted.","Ensure optional schedule fields with defaults (e.g. Minute=0) are not treated as 'unset' and skipped."],"exampleFix":"// before\nvalue => panic!(\"expected StartCalendarInterval dictionary, got {value:?}\"),\n// after\nvalue => panic!(\"expected StartCalendarInterval dictionary, got {value:?}; keys: {:?}\", dict.keys().collect::<Vec<_>>()),","handlingStrategy":"type-guard","validationCode":"// rust\nif !dict.contains_key(\"StartCalendarInterval\") {\n    return Err(\"plist missing StartCalendarInterval\".into());\n}","typeGuard":"fn calendar_interval(dict: &Dictionary) -> Option<&Dictionary> {\n    match dict.get(\"StartCalendarInterval\") {\n        Some(Value::Dictionary(d)) => Some(d),\n        _ => None,\n    }\n}","tryCatchPattern":"// rust\nmatch dict.get(\"StartCalendarInterval\") {\n    Some(Value::Dictionary(interval)) => /* assert Hour/Minute */,\n    other => return Err(anyhow!(\"expected StartCalendarInterval dictionary, got {other:?}\")),\n}","preventionTips":["Test every optional launchd key (StartCalendarInterval, WorkingDirectory) individually.","Ensure schedule defaults (Minute=0) still emit the key.","Keep key names centralized to avoid typos/renames.","Assert nested dictionary structure, not just key presence."],"tags":["rust","test-assertion","plist","launchd"],"backgroundTag":"unexpected-response-shape","analyzedSha":"afd2eddd3a50c16190efc1c7e94404b48f72af57","analyzedAt":"2026-09-09T01:38:25.179Z","contentChangedAt":"2026-09-09T01:38:25.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}