{"record":{"id":"d1484c7d33a03966","repo":"jdx/mise","slug":"expected-startcalendarinterval-array-got-value","errorCode":null,"errorMessage":"expected StartCalendarInterval array, got {value:?}","messagePattern":"expected StartCalendarInterval array, got (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/system/launchd.rs","lineNumber":878,"sourceCode":"        match dict.get(\"StartCalendarInterval\") {\n            Some(Value::Array(intervals)) => {\n                assert_eq!(intervals.len(), 2);\n                match &intervals[0] {\n                    Value::Dictionary(interval) => {\n                        assert_eq!(interval.get(\"Hour\"), Some(&Value::Integer(3.into())));\n                        assert_eq!(interval.get(\"Minute\"), Some(&Value::Integer(0.into())));\n                    }\n                    value => panic!(\"expected first calendar interval dictionary, got {value:?}\"),\n                }\n                match &intervals[1] {\n                    Value::Dictionary(interval) => {\n                        assert_eq!(interval.get(\"Hour\"), Some(&Value::Integer(12.into())));\n                        assert_eq!(interval.get(\"Weekday\"), Some(&Value::Integer(1.into())));\n                    }\n                    value => panic!(\"expected second calendar interval dictionary, got {value:?}\"),\n                }\n            }\n            value => panic!(\"expected StartCalendarInterval array, got {value:?}\"),\n        }\n        assert_eq!(dict.get(\"ThrottleInterval\"), None);\n        assert_eq!(dict.get(\"QueueDirectories\"), None);\n    }\n\n    #[test]\n    fn test_render_plist_throttle_and_queue_directories() {\n        let request = LaunchdRequest::from_toml(\n            \"sync\".to_string(),\n            LaunchdTomlConfig {\n                program: Some(\"/bin/echo\".to_string()),\n                throttle_interval: Some(10),\n                nice: None,\n                queue_directories: vec![\n                    \"~/Library/Queues/sync\".to_string(),\n                    \"/var/spool/sync\".to_string(),\n                ],\n                ..Default::default()","sourceCodeStart":860,"sourceCodeEnd":896,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/system/launchd.rs#L860-L896","documentation":"Test assertion panic in test_render_plist_multiple_calendar_intervals. When a request carries two calendar schedules, the rendered plist must expose them as an array under 'StartCalendarInterval'; if the key is missing or holds a single dictionary/other type, this panic fires with the actual value.","triggerScenarios":"render_plist serializes a two-interval request as a single StartCalendarInterval dictionary instead of an array (or omits the key), so dict.get(\"StartCalendarInterval\") is not Some(Value::Array(_)).","commonSituations":"A refactor that always writes StartCalendarInterval as a bare dictionary (valid for a single schedule but wrong for multiple), regressing launchd's multi-interval array form.","solutions":["Inspect the {value:?} payload to see what render_plist emitted for StartCalendarInterval","Make render_plist emit an array of interval dictionaries when more than one schedule exists (launchd accepts a single dict only for one schedule)","Re-run the test to confirm both intervals appear with correct Hour/Minute/Weekday keys"],"exampleFix":"// before\n\"StartCalendarInterval\" => Value::Dictionary(interval)\n// after\n\"StartCalendarInterval\" => Value::Array(intervals) // when request.calendar_intervals.len() > 1","handlingStrategy":"validation","validationCode":"match dict.get(\"StartCalendarInterval\") {\n    Some(Value::Array(a)) if a.len() > 1 => {},\n    Some(Value::Dictionary(_)) | None => panic!(\n        \"multi-schedule request must render StartCalendarInterval as array, got {:?}\",\n        dict.get(\"StartCalendarInterval\")\n    ),\n    other => panic!(\"unexpected StartCalendarInterval: {other:?}\"),\n}","typeGuard":"fn is_multi_interval(v: Option<&Value>) -> bool {\n    matches!(v, Some(Value::Array(a)) if a.len() > 1)\n}","tryCatchPattern":null,"preventionTips":["Remember launchd accepts a bare dict only for one schedule; arrays for many — encode that rule in the serializer","Add a unit test asserting the dict-vs-array switch at the len()==1 boundary"],"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-14T00:17:10.932Z"}