{"record":{"id":"10c362377247c8c2","repo":"jdx/mise","slug":"remote-cache-directory-contains-duplicate-names","errorCode":null,"errorMessage":"remote cache directory contains duplicate names","messagePattern":"remote cache directory contains duplicate names","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/task/task_cache_store.rs","lineNumber":729,"sourceCode":"    let mut pending = vec![(PathBuf::new(), root.clone(), BTreeSet::new())];\n    let mut nodes = BTreeMap::<PathBuf, RestoredNode>::new();\n    while let Some((path, digest, mut ancestors)) = pending.pop() {\n        if !ancestors.insert(digest.clone()) {\n            bail!(\"remote cache directory graph contains a cycle\");\n        }\n        let bytes = store.client.get_blob(&digest, DIRECTORY_MEDIA_TYPE).await?;\n        let directory: RemoteDirectory = serde_json::from_slice(&bytes)?;\n        if canonical_json(&serde_json::to_value(&directory)?)? != bytes {\n            bail!(\"remote cache directory is not canonical JSON\");\n        }\n        if directory.version != 1 {\n            bail!(\"unsupported remote cache directory version\");\n        }\n        let mut names = BTreeSet::new();\n        for directory in directory.directories {\n            validate_cache_name(&directory.name)?;\n            if !names.insert(directory.name.clone()) {\n                bail!(\"remote cache directory contains duplicate names\");\n            }\n            let child = path.join(&directory.name);\n            validate_cache_path(&child)?;\n            nodes.insert(\n                child.clone(),\n                RestoredNode::Directory {\n                    mode: directory.mode,\n                },\n            );\n            pending.push((child, directory.digest, ancestors.clone()));\n        }\n        for file in directory.files {\n            validate_cache_name(&file.name)?;\n            if !names.insert(file.name.clone()) {\n                bail!(\"remote cache directory contains duplicate names\");\n            }\n            let child = path.join(&file.name);\n            validate_cache_path(&child)?;","sourceCodeStart":711,"sourceCodeEnd":747,"githubUrl":"https://github.com/jdx/mise/blob/9dcfcaa0dc8747a2577d3270b69bb9d8313b2807/src/task/task_cache_store.rs#L711-L747","documentation":"Inside each fetched directory proto, mise collects entry names into one BTreeSet shared across the directories, files, and symlinks lists. A name repeated within the directories list - or colliding with a name already seen - bails with 'remote cache directory contains duplicate names' during restore.","triggerScenarios":"materialize_remote_tree processes a directory blob whose directories array lists the same name twice (or reuses a name inserted earlier while iterating the entries).","commonSituations":"Corrupt or hand-merged blobs; non-mise writers producing protos; a server serving a mismatched blob for a digest.","solutions":["Purge the entry from the remote cache and re-run the task","Ensure a single-writer mise version across the team","Inspect the directory blob at the failing digest and fix or delete it"],"exampleFix":"// directory proto\n// before\n\"directories\": [{\"name\": \"out\"}, {\"name\": \"out\"}]\n// after\n\"directories\": [{\"name\": \"out\"}]","handlingStrategy":"fallback","validationCode":"// when generating protos, enforce unique names before upload:\nlet mut names = std::collections::BTreeSet::new();\nfor d in &proto.directories {\n    if !names.insert(d.name.clone()) {\n        return Err(eyre::bail!(\"duplicate directory name {}\", d.name));\n    }\n}","typeGuard":"fn directory_names_unique(directories: &[RemoteDirectoryNode]) -> bool {\n    let mut names = std::collections::BTreeSet::new();\n    directories.iter().all(|d| names.insert(d.name.clone()))\n}","tryCatchPattern":"match materialize_remote_tree(&store, &root).await {\n    Ok(tree) => tree,\n    Err(err) if err.to_string().contains(\"duplicate names\") => {\n        warn!(\"corrupt directory proto at digest; purging entry\");\n        purge_action_result(&key).await.ok();\n        run_task_uncached(&task).await?\n    }\n    Err(err) => return Err(err),\n}","preventionTips":["Generate directory protos with sorted, deduplicated name lists","Validate uniqueness server-side if you accept proto uploads","Treat duplicate-name errors as corruption: purge and rebuild, do not hand-patch blobs in place"],"tags":["mise","remote-cache","task-cache","duplicate-entry","integrity"],"backgroundTag":"duplicate-manifest-entry","analyzedSha":"9dcfcaa0dc8747a2577d3270b69bb9d8313b2807","analyzedAt":"2026-08-17T14:28:50.624Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}