{"record":{"id":"a61dad6494c813f8","repo":"conductor-oss/conductor","slug":"skill-package-contains-duplicate-path-path","errorCode":null,"errorMessage":"Skill package contains duplicate path: {path}","messagePattern":"Skill package contains duplicate path: (.+?)","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"agentspan/src/main/java/org/conductoross/conductor/ai/agentspan/runtime/service/SkillRegistryService.java","lineNumber":442,"sourceCode":"\n    @SuppressWarnings(\"unchecked\")\n    private ParsedSkillPackage parseSkillPackage(byte[] bytes, Map<String, Object> manifest) {\n        List<SkillFileEntry> files = new ArrayList<>();\n        Map<String, byte[]> contentByPath = new TreeMap<>();\n        long totalUncompressedBytes = 0;\n        try (ZipInputStream zip = new ZipInputStream(new ByteArrayInputStream(bytes))) {\n            ZipEntry entry;\n            while ((entry = zip.getNextEntry()) != null) {\n                if (entry.isDirectory()) {\n                    continue;\n                }\n                if (files.size() >= maxFileCount) {\n                    throw new IllegalArgumentException(\n                            \"Skill package exceeds max file count of \" + maxFileCount);\n                }\n                String path = normalizeEntryName(entry.getName());\n                if (contentByPath.containsKey(path)) {\n                    throw new IllegalArgumentException(\n                            \"Skill package contains duplicate path: \" + path);\n                }\n                MessageDigest digest = MessageDigest.getInstance(\"SHA-256\");\n                long size = 0;\n                ByteArrayOutputStream content = new ByteArrayOutputStream();\n                byte[] buffer = new byte[8192];\n                int read;\n                while ((read = zip.read(buffer)) >= 0) {\n                    digest.update(buffer, 0, read);\n                    content.write(buffer, 0, read);\n                    size += read;\n                    totalUncompressedBytes += read;\n                    if (size > maxPackageBytes) {\n                        throw new IllegalArgumentException(\n                                \"Skill package contains oversized file: \" + path);\n                    }\n                    if (totalUncompressedBytes > maxUncompressedBytes) {\n                        throw new IllegalArgumentException(","sourceCodeStart":424,"sourceCodeEnd":460,"githubUrl":"https://github.com/conductor-oss/conductor/blob/cf7c3e4a8adfb158be778ab1ec525323c363cd3a/agentspan/src/main/java/org/conductoross/conductor/ai/agentspan/runtime/service/SkillRegistryService.java#L424-L460","documentation":"Thrown by parseSkillPackage when two zip entries normalize to the same path. normalizeEntryName converts backslashes to slashes and strips leading './', so entries like 'foo/./bar' and 'foo/bar', or 'a\\\\b' and 'a/b', collide. This is a package integrity check preventing ambiguous content.","triggerScenarios":"POST /api/skills/register with a zip that contains both 'SKILL.md' and './SKILL.md', or 'scripts\\run.sh' alongside 'scripts/run.sh'. Also when a zip tool emits a path twice with different separators.","commonSituations":"Building the zip on Windows producing backslash paths mixed with forward slashes; a packaging script that adds a './' prefix to some entries; zipping the same file from two source locations into the same target path.","solutions":["Rebuild the zip ensuring each logical path appears exactly once with forward slashes and no './' prefix.","On Windows, use a zip tool that normalizes to forward slashes, or normalize paths in the build script.","List the zip contents (unzip -l) and dedupe before uploading."],"exampleFix":"# before: zip contains both ./SKILL.md and SKILL.md\n# after: rebuild without path prefixes\nzip skill.zip SKILL.md scripts/run.sh references/doc.md","handlingStrategy":"validation","validationCode":"// Before uploading, detect duplicate normalized paths\nSet<String> seen = new HashSet<>();\ntry (var z = new ZipFile(packageFile)) {\n    for (var e : Collections.list(z.entries())) {\n        if (e.isDirectory()) continue;\n        String norm = e.getName().replace('\\\\','/').replaceAll(\"^\\\\./\", \"\");\n        if (!seen.add(norm)) throw new IllegalStateException(\"duplicate path: \" + norm);\n    }\n}","typeGuard":"static boolean noDuplicatePaths(java.io.File zip) throws java.io.IOException {\n    Set<String> seen = new HashSet<>();\n    try (var z = new ZipFile(zip)) {\n        for (var e : Collections.list(z.entries())) {\n            if (e.isDirectory()) continue;\n            if (!seen.add(e.getName().replace('\\\\','/').replaceAll(\"^\\\\./\",\"\"))) return false;\n        }\n    }\n    return true;\n}","tryCatchPattern":"try { skillRegistryService.register(manifest, pkg); }\ncatch (IllegalArgumentException e) {\n    if (e.getMessage().contains(\"duplicate path\")) { /* rebuild zip with unique paths */ }\n    else throw e;\n}","preventionTips":["Build zips on POSIX paths and never prefix entries with './'.","List zip contents (unzip -l) and dedupe before uploading."],"tags":["java","conductor","skill-registry","zip","validation","package-integrity"],"backgroundTag":null,"analyzedSha":"cf7c3e4a8adfb158be778ab1ec525323c363cd3a","analyzedAt":"2026-08-14T03:33:19.897Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}