jdx/mise · error
task cache manifest contains duplicate or nested roots
Error message
task cache manifest contains duplicate or nested roots
What it means
On cache restore, the manifest's roots list must already be deduplicated and non-nested — remove_nested_roots(manifest.roots) must return an identical list. If normalizing the list changes it, the manifest contains duplicate or nested roots, which a correctly-written mise manifest never has, so the restore bails as a corruption guard.
Source
Thrown at src/task/task_cache.rs:567
if let Err(err) = self
.store
.remove_local(&self.key, self.action.len() as u64)
.await
{
warn!(
"failed to remove expired local task cache entry {}: {err}",
self.key
);
}
return Ok(TaskCacheRestore::Miss(TaskCacheMissReason::Expired));
}
let restore = || -> Result<TaskCacheHit> {
let manifest = self.read_manifest(&entry.manifest)?;
for root in &manifest.roots {
ensure_safe_relative(root)?;
}
if remove_nested_roots(manifest.roots.clone()) != manifest.roots {
bail!("task cache manifest contains duplicate or nested roots");
}
verify_artifact_checksum(
&manifest,
entry.artifact.as_ref().map(|artifact| artifact.path()),
)?;
if manifest.roots.is_empty() {
self.store.touch(&self.key);
return Ok(TaskCacheHit {
output: manifest.output,
restored_bytes: manifest.restored_bytes,
saved_duration: std::time::Duration::from_nanos(manifest.execution_duration_ns),
});
}
// Serialize restores targeting the same working directory across
// mise processes so validation and renames form one cooperative
// critical section. Result-only entries never mutate the task root.
let _output_lock = crate::lock_file::LockFile::new(
&self.root.join(".mise-task-artifact-cache-output"),View on GitHub (pinned to 6f52dcdf99)
Solutions
- Delete the offending entry directory under the task-artifacts cache and re-run the task to repopulate it
- If it recurs, wipe the whole task cache dir and let it rebuild
- Ensure only one writer per cache key (entry locks exist, but shared-remote or NFS setups can bypass them)
- Check for disk-full or fsync failures on the cache volume
Example fix
# shell
key=$(ls ~/.cache/task-artifacts/v2 | head -n1) # locate suspect entries
rm -rf "${XDG_CACHE_HOME:-$HOME/.cache}/task-artifacts/v2" # full reset
mise run build # repopulates a clean cache Defensive patterns
Strategy: fallback
Validate before calling
# detect duplicate/nested roots in a manifest before restore (jq)
jq -r '.roots | sort | length as $n | (map({c: (split("/") | length)}) | length) as $x | $n' entry/manifest.json 2>/dev/null || echo 'no manifest' Try / catch
Catch the restore failure, delete the entry directory under task-artifacts/v2, re-run the task uncached to regenerate, and continue — treat it as a disposable cache, never as source of truth.
Prevention
- Ensure one writer per cache key (serialize CI jobs per task)
- Never hand-edit cache manifests
- Monitor cache-dir disk space
When it happens
Trigger: Restoring an entry whose manifest was hand-edited, truncated and re-written, or produced by a concurrent writer racing on the same cache key; manifests are JSON in the entry directory and any non-atomic write can leave an inconsistent roots array.
Common situations: Two CI jobs writing the same task cache key simultaneously; disk-full during manifest write; users experimenting inside the cache directory; a buggy third-party tool touching the cache.
Related errors
- task cache archive is missing {}
- task cache manifest does not match cache key
- task {} cache outputs must not contain source {}
- unsupported task cache store version {}; expected {}
- task cache artifact checksum mismatch
AI-assisted analysis of jdx/mise@6f52dcdf99 (2026-08-22).
Data as JSON: /api/errors/6c53c5d7f3900108.
Report an issue: GitHub.