{"record":{"id":"15abf26c50be47d1","repo":"jdx/mise","slug":"task-cache-archive-contains-duplicate-paths","errorCode":null,"errorMessage":"task cache archive contains duplicate paths","messagePattern":"task cache archive contains duplicate paths","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/task/task_cache_store.rs","lineNumber":613,"sourceCode":"                    size,\n                },\n                executable: mode & 0o111 != 0,\n                mode,\n                file: temporary,\n            }\n        } else if entry_type == EntryType::Symlink {\n            let target = entry\n                .header()\n                .link_name()\n                .ok_or_else(|| eyre!(\"remote cache symlink is missing its target\"))?\n                .into_owned();\n            validate_cache_symlink_target(&entry_path, &target)?;\n            ArchiveNode::Symlink { mode, target }\n        } else {\n            bail!(\"unsupported task cache archive entry type\");\n        };\n        if nodes.insert(entry_path.clone(), node).is_some() {\n            bail!(\"task cache archive contains duplicate paths\");\n        }\n        let mut parent = entry_path.parent();\n        while let Some(path) = parent {\n            nodes\n                .entry(path.to_path_buf())\n                .or_insert(ArchiveNode::Directory { mode: 0o755 });\n            parent = path.parent();\n        }\n    }\n\n    fn build_directory(\n        path: &Path,\n        nodes: &BTreeMap<PathBuf, ArchiveNode>,\n        directory_uploads: &mut Vec<BlobUpload>,\n    ) -> Result<CacheDigest> {\n        let mut directories = Vec::new();\n        let mut files = Vec::new();\n        let mut symlinks = Vec::new();","sourceCodeStart":595,"sourceCodeEnd":631,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/task/task_cache_store.rs#L595-L631","documentation":"When ingesting a task-cache archive into the CAS, each entry path is inserted into a BTreeMap of nodes; if the same path appears twice, insert returns Some and mise bails. Duplicate paths would make the restored directory tree ambiguous, so the archive is rejected.","triggerScenarios":"archive_to_cas encounters two archive members with the identical path — e.g. a tar built by concatenating archives, an archive listing a file both explicitly and via its parent traversal, or a producer that added the same path twice.","commonSituations":"Archives assembled by appending multiple tar streams (`tar -Af` mistakes); custom cache upload scripts that re-add updated files without replacing old entries; corrupted or maliciously crafted cache payloads from a remote store.","solutions":["Rebuild the archive ensuring each path appears exactly once (create a fresh tar instead of appending).","Inspect duplicates with `tar -tf archive.tar | sort | uniq -d` and remove the offending entries.","Delete the bad cache entry from the remote store and re-run the task to regenerate a clean archive.","Fix the producing script so it replaces entries rather than appending them."],"exampleFix":"// before: appending produces duplicate paths\ntar -rf cache.tar dir/file\ntar -rf cache.tar dir/file\n// after: single pass, no duplicates\ntar -cf cache.tar dir","handlingStrategy":"validation","validationCode":"const names = entries.map(e => e.path);\nconst dupes = names.filter((n, i) => names.indexOf(n) !== i);\nif (dupes.length) throw new Error(`duplicate archive paths: ${dupes}`);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Create archives in a single pass instead of appending/concatenating.","Check with `tar -tf a.tar | sort | uniq -d` before upload.","Never merge cache archives from multiple runs."],"tags":["task-cache","archive","duplicate-paths","validation"],"backgroundTag":"internal-invariant-violation","analyzedSha":"533346cc374382b41ec5ff70536252b2e96e725c","analyzedAt":"2026-09-17T13:35:38.149Z","contentChangedAt":"2026-09-17T13:35:38.149Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}