jdx/mise · error

task {} cache outputs must not contain source {}

Error message

task {} cache outputs must not contain source {}

What it means

While building the task artifact cache, mise resolves the task's output roots and input source paths, then verifies no source equals or lies beneath an output root. Overlap would make the cache feed its own restored outputs back in as inputs (and rewrite the cache key every run), so the builder bails.

Source

Thrown at src/task/task_cache.rs:283

        let root = task_cwd(task, config).await?;
        validate_config(task, &root)?;
        let output_roots = resolve_output_roots(task, &root, false)?;
        for output in &output_roots {
            ensure_no_symlink_ancestors(&root, output)?;
        }
        let Some(inputs) = task_cache_inputs(task, config, !dry_run).await? else {
            warn!(
                "task {} has sources defined but no matching files found; artifact caching disabled",
                task.name
            );
            return Ok(None);
        };
        for source in &inputs.source_paths {
            if output_roots
                .iter()
                .any(|output| source == output || source.starts_with(output))
            {
                bail!(
                    "task {} cache outputs must not contain source {}",
                    task.name,
                    source.display()
                );
            }
        }
        Ok(Some(TaskArtifactCacheBuilder {
            root,
            inputs,
            output_roots,
        }))
    }
}

impl TaskArtifactCacheBuilder {
    /// Finishes cache-key construction after task tools, environment, and
    /// dependency artifacts have been resolved.
    pub(crate) async fn finish(self, ctx: TaskCacheContext<'_>) -> Result<TaskArtifactCache> {

View on GitHub (pinned to 6f52dcdf99)

Solutions

  1. Point outputs at a separate directory from sources (e.g. outputs = ["dist/**"])
  2. If the task has no meaningful files to cache, declare outputs = [] and rely on command_inputs for keying
  3. Narrow sources or outputs so no output root is a prefix of any source path
  4. For in-place transforms, disable artifact caching and use run = "always" semantics instead

Example fix

# before
[tasks.codegen]
sources = ["src/schema/**"]
outputs = ["src/**"]

# after
[tasks.codegen]
sources = ["src/schema/**"]
outputs = ["src/generated/**"]
Defensive patterns

Strategy: validation

Validate before calling

# rough overlap check before enabling cache
python3 - <<'EOF'
import tomllib, sys
cfg = tomllib.load(open('mise.toml','rb'))
for name, t in cfg.get('tasks', {}).items():
    if not t.get('cache'): continue
    for s in t.get('sources', []):
        base = s.rstrip('*').rstrip('/')
        for o in t.get('outputs', []):
            ob = o.rstrip('*').rstrip('/')
            if base and ob and (base.startswith(ob) or ob.startswith(base)):
                sys.exit(f"{name}: output '{o}' overlaps source '{s}'")
print('ok')
EOF

Prevention

When it happens

Trigger: A cached task with sources = ["src/**"] and outputs = ["src/**"], or outputs = ["."] / outputs = [""] whose resolved root contains every source path; the check runs in TaskArtifactCacheBuilder construction after task_cache_inputs returns concrete source_paths.

Common situations: In-place codegen tasks that rewrite their inputs; broad outputs like '.' used to cache 'anything that changed'; tasks that emit next to sources (e.g. *.ts -> *.js in the same tree) without excluding the sources.

Related errors


AI-assisted analysis of jdx/mise@6f52dcdf99 (2026-08-22). Data as JSON: /api/errors/8bfe6b5aef3463d2. Report an issue: GitHub.