{"record":{"id":"9c892aede4799be9","repo":"jdx/mise","slug":"task-cache-requires-at-least-one-source","errorCode":null,"errorMessage":"task {} cache requires at least one source","messagePattern":"task (.+?) cache requires at least one source","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/task/task_cache.rs","lineNumber":1205,"sourceCode":"}\n\nfn display_cache_text(text: &str) -> String {\n    text.escape_debug().to_string()\n}\n\n/// Returns the versioned directory containing task artifact cache entries.\npub(crate) fn task_cache_dir() -> PathBuf {\n    Settings::get()\n        .task\n        .cache_dir\n        .clone()\n        .unwrap_or_else(|| dirs::CACHE.join(\"task-artifacts\"))\n        .join(CACHE_DIR_VERSION)\n}\n\npub(crate) fn validate_config(task: &Task, root: &Path) -> Result<()> {\n    if task.sources.is_empty() {\n        bail!(\"task {} cache requires at least one source\", task.name);\n    }\n    if task.outputs.is_auto() {\n        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    }","sourceCodeStart":1187,"sourceCodeEnd":1223,"githubUrl":"https://github.com/jdx/mise/blob/6f52dcdf99e282ef7a7db68c81301fa4618d0f79/src/task/task_cache.rs#L1187-L1223","documentation":"validate_config rejects a task enrolled in artifact caching when task.sources is empty. The cache key is derived from source file contents, so with no sources there is nothing to key or invalidate on — caching is meaningless and the config is refused up front.","triggerScenarios":"Enabling [task.cache] (or the experimental task artifact caching setting) on a task that declares no sources = [...] list; the check is the first one in validate_config and fires during config validation before any run.","commonSituations":"Adding cache = true to a deploy or one-off task that only shells out; forgetting the sources line when copying a cached task template; assuming command_inputs alone is enough.","solutions":["Declare the inputs the task consumes: sources = [\"src/**\", \"mise.toml\"]","If the task genuinely has no file inputs, use command_inputs for keying and still declare at least one source (e.g. the config file)","Remove caching from the task if it is inherently non-cacheable"],"exampleFix":"# before\n[tasks.build]\ncache = true\nrun = \"cargo build\"\n\n# after\n[tasks.build]\ncache = true\nsources = [\"Cargo.toml\", \"Cargo.lock\", \"src/**\"]\nrun = \"cargo build\"","handlingStrategy":"validation","validationCode":"python3 - <<'EOF'\nimport tomllib, sys\ncfg = tomllib.load(open('mise.toml','rb'))\nfor name, t in cfg.get('tasks', {}).items():\n    if t.get('cache') and not t.get('sources'):\n        sys.exit(f'{name}: cached task needs sources')\nprint('ok')\nEOF","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always pair cache = true with a sources list","Include lockfiles and config files in sources for correct invalidation"],"tags":["mise","task-cache","caching","configuration","validation"],"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"}