{"record":{"id":"3318f5a374c45638","repo":"jdx/mise","slug":"task-cache-outputs-require-at-least-one-non-exc","errorCode":null,"errorMessage":"task {} cache outputs require at least one non-excluded pattern","messagePattern":"task (.+?) cache outputs require at least one non-excluded pattern","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/task/task_cache.rs","lineNumber":1226,"sourceCode":"        bail!(\n            \"task {} cache requires explicit outputs or outputs = []\",\n            task.name\n        );\n    }\n    if let Some(command) = task.cache.as_ref().and_then(|cache| {\n        cache\n            .command_inputs\n            .iter()\n            .find(|command| command.trim().is_empty())\n    }) {\n        bail!(\n            \"task {} cache command input must not be empty: {command:?}\",\n            task.name\n        );\n    }\n    let patterns = task.outputs.patterns();\n    if !patterns.is_empty() && output_glob_patterns(&patterns).is_empty() {\n        bail!(\n            \"task {} cache outputs require at least one non-excluded pattern\",\n            task.name\n        );\n    }\n    for output in &patterns {\n        let path = if let Some(body) = output.strip_prefix('!') {\n            PathBuf::from(body)\n        } else if let Some(body) = output.strip_prefix(\"\\\\!\") {\n            PathBuf::from(format!(\"!{body}\"))\n        } else {\n            PathBuf::from(&output)\n        };\n        ensure_safe_relative(&path).wrap_err_with(|| {\n            format!(\n                \"task {} cache output must stay within {}: {output}\",\n                task.name,\n                root.display()\n            )","sourceCodeStart":1208,"sourceCodeEnd":1244,"githubUrl":"https://github.com/jdx/mise/blob/6f52dcdf99e282ef7a7db68c81301fa4618d0f79/src/task/task_cache.rs#L1208-L1244","documentation":"validate_config takes the task's output patterns and computes output_glob_patterns(&patterns) — the list with exclusions ('!...') filtered out. If patterns is non-empty but every entry is an exclusion, there is nothing to save and the config is rejected with 'cache outputs require at least one non-excluded pattern'.","triggerScenarios":"outputs = [\"!tmp/**\"] or outputs = [\"!*.log\", \"!cache/**\"] — only negated patterns, so output_glob_patterns returns an empty vec while patterns is not empty.","commonSituations":"Starting from an exclusion list intending to add includes later; refactoring outputs and accidentally deleting the inclusion lines; misunderstanding that '!' entries only filter, never select.","solutions":["Add at least one inclusion pattern, e.g. outputs = [\"dist/**\", \"!dist/tmp/**\"]","If the task truly writes nothing, use outputs = [] instead of exclusion-only lists","Lint task configs for outputs arrays containing only '!'-prefixed entries"],"exampleFix":"# before\n[tasks.pack]\noutputs = [\"!dist/*.tmp\"]\n\n# after\n[tasks.pack]\noutputs = [\"dist/**\", \"!dist/*.tmp\"]","handlingStrategy":"validation","validationCode":"python3 - <<'EOF'\nimport tomllib, sys\ncfg = tomllib.load(open('mise.toml','rb'))\nfor name, t in cfg.get('tasks', {}).items():\n    outs = [str(o) for o in t.get('outputs', [])]\n    if outs and all(o.lstrip('\\\\').startswith('!') for o in outs):\n        sys.exit(f'{name}: outputs contains only exclusions')\nprint('ok')\nEOF","typeGuard":null,"tryCatchPattern":null,"preventionTips":["'!' patterns only filter — always include at least one inclusion","outputs = [] for no-file tasks, not an exclusion-only list"],"tags":["mise","task-cache","caching","configuration","validation","globs"],"backgroundTag":"task-cache-config-invalid","analyzedSha":"6f52dcdf99e282ef7a7db68c81301fa4618d0f79","analyzedAt":"2026-08-22T10:14:23.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}