{"record":{"id":"df575e64aa171277","repo":"jdx/mise","slug":"pending-inline-overlays-should-be-present","errorCode":null,"errorMessage":"pending inline overlays should be present","messagePattern":"pending inline overlays should be present","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/config/mod.rs","lineNumber":4681,"sourceCode":"/// When the same name appears in more than one file task (e.g. a local\n/// `.mise/tasks` script and a same-named task from a `git::` include), the last\n/// one wins. Callers load `file_tasks` in declared `task_config.includes`\n/// order, so the later include in the list takes precedence — see\n/// `load_tasks_in_dir`.\nfn merge_file_and_config_tasks(file_tasks: Vec<Task>, config_tasks: Vec<Task>) -> Vec<Task> {\n    let mut by_name: IndexMap<String, Task> = IndexMap::new();\n    for t in prefer_windows_file_task_siblings(file_tasks) {\n        by_name.insert(t.name.clone(), t);\n    }\n    let mut seen_config_task_names = BTreeSet::new();\n    let mut pending_inline_overlays: IndexMap<String, Vec<Task>> = IndexMap::new();\n    for t in config_tasks {\n        if !seen_config_task_names.insert(t.name.clone()) {\n            let has_command = !t.run.is_empty() || !t.run_windows.is_empty() || t.file.is_some();\n            if pending_inline_overlays.contains_key(&t.name) && has_command {\n                let overlays = pending_inline_overlays\n                    .shift_remove(&t.name)\n                    .expect(\"pending inline overlays should be present\");\n                let mut base = t;\n                for overlay in overlays.into_iter().rev() {\n                    base.merge_toml_overlay(overlay);\n                }\n                by_name.insert(base.name.clone(), base);\n            } else if let Some(overlays) = pending_inline_overlays.get_mut(&t.name) {\n                overlays.push(t);\n            }\n            continue;\n        }\n        if let Some(existing) = by_name\n            .get_mut(&t.name)\n            .filter(|existing| existing.is_toml_include)\n        {\n            if t.config_precedence <= existing.config_precedence {\n                if t.run.is_empty() && t.run_windows.is_empty() && t.file.is_none() {\n                    existing.merge_toml_overlay(t);\n                } else {","sourceCodeStart":4663,"sourceCodeEnd":4699,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/config/mod.rs#L4663-L4699","documentation":"mise panics with 'pending inline overlays should be present' when merging inline task overlays: `shift_remove(&t.name)` is called only after `pending_inline_overlays.contains_key(&t.name)` returned true in the same iteration, yet the remove still returns `None`. Since no mutation happens between the check and the remove, this indicates a logic break in the overlay bookkeeping (duplicate task names triggering the merge path with an inconsistent map).","triggerScenarios":"Duplicate task names in `config_tasks` where a task has a command (`run`/`run_windows`/`file`) and its name is in `pending_inline_overlays`, but the remove finds nothing — e.g. the name was already removed by an earlier iteration of the same loop processing another duplicate, or the map keys diverge from task names due to normalization.","commonSituations":"Configs defining the same inline task name multiple times (same file or across layered files) so the seen-set insert fails repeatedly; name normalization (path prefixes, case) making `contains_key` and `shift_remove` disagree; refactors of the overlay-merge loop.","solutions":["Use `shift_remove` directly and branch on its return value instead of a separate `contains_key` check","Verify each name enters the merge branch at most once (track processed names)","Confirm overlay map keys use exactly the same task-name string as `t.name`","Reproduce with two duplicate inline task definitions to see which iteration removes the entry"],"exampleFix":"// before\nif pending_inline_overlays.contains_key(&t.name) && has_command {\n    let overlays = pending_inline_overlays.shift_remove(&t.name)\n        .expect(\"pending inline overlays should be present\");\n// after\nif has_command {\n    if let Some(overlays) = pending_inline_overlays.shift_remove(&t.name) {\n        // merge overlays\n    }","handlingStrategy":"type-guard","validationCode":"let Some(overlays) = pending_inline_overlays.shift_remove(&t.name) else { continue; };","typeGuard":"pending_inline_overlays.contains_key(&t.name)","tryCatchPattern":null,"preventionTips":["Branch directly on shift_remove's Option instead of contains_key + remove","Ensure each duplicate task name is processed exactly once","Keep overlay map keys byte-identical to task names"],"tags":["rust","tasks","config","panic","overlay"],"backgroundTag":"internal-invariant-violation","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"}