{"record":{"id":"6c53c5d7f3900108","repo":"jdx/mise","slug":"task-cache-manifest-contains-duplicate-or-nested-r","errorCode":null,"errorMessage":"task cache manifest contains duplicate or nested roots","messagePattern":"task cache manifest contains duplicate or nested roots","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/task/task_cache.rs","lineNumber":567,"sourceCode":"            if let Err(err) = self\n                .store\n                .remove_local(&self.key, self.action.len() as u64)\n                .await\n            {\n                warn!(\n                    \"failed to remove expired local task cache entry {}: {err}\",\n                    self.key\n                );\n            }\n            return Ok(TaskCacheRestore::Miss(TaskCacheMissReason::Expired));\n        }\n        let restore = || -> Result<TaskCacheHit> {\n            let manifest = self.read_manifest(&entry.manifest)?;\n            for root in &manifest.roots {\n                ensure_safe_relative(root)?;\n            }\n            if remove_nested_roots(manifest.roots.clone()) != manifest.roots {\n                bail!(\"task cache manifest contains duplicate or nested roots\");\n            }\n            verify_artifact_checksum(\n                &manifest,\n                entry.artifact.as_ref().map(|artifact| artifact.path()),\n            )?;\n            if manifest.roots.is_empty() {\n                self.store.touch(&self.key);\n                return Ok(TaskCacheHit {\n                    output: manifest.output,\n                    restored_bytes: manifest.restored_bytes,\n                    saved_duration: std::time::Duration::from_nanos(manifest.execution_duration_ns),\n                });\n            }\n            // Serialize restores targeting the same working directory across\n            // mise processes so validation and renames form one cooperative\n            // critical section. Result-only entries never mutate the task root.\n            let _output_lock = crate::lock_file::LockFile::new(\n                &self.root.join(\".mise-task-artifact-cache-output\"),","sourceCodeStart":549,"sourceCodeEnd":585,"githubUrl":"https://github.com/jdx/mise/blob/6f52dcdf99e282ef7a7db68c81301fa4618d0f79/src/task/task_cache.rs#L549-L585","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# shell\nkey=$(ls ~/.cache/task-artifacts/v2 | head -n1) # locate suspect entries\nrm -rf \"${XDG_CACHE_HOME:-$HOME/.cache}/task-artifacts/v2\"  # full reset\nmise run build  # repopulates a clean cache","handlingStrategy":"fallback","validationCode":"# detect duplicate/nested roots in a manifest before restore (jq)\njq -r '.roots | sort | length as $n | (map({c: (split(\"/\") | length)}) | length) as $x | $n' entry/manifest.json 2>/dev/null || echo 'no manifest'","typeGuard":null,"tryCatchPattern":"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.","preventionTips":["Ensure one writer per cache key (serialize CI jobs per task)","Never hand-edit cache manifests","Monitor cache-dir disk space"],"tags":["mise","task-cache","caching","corruption","manifest"],"backgroundTag":"cache-manifest-corruption","analyzedSha":"6f52dcdf99e282ef7a7db68c81301fa4618d0f79","analyzedAt":"2026-08-22T10:14:23.840Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}