conductor-oss/conductor · error · IllegalArgumentException

Skill package contains duplicate script tool name

Error message

Skill package contains duplicate script tool name: {toolName}

What it means

Thrown by parseSkillPackage when two script files under scripts/ map to the same tool name. scriptToolName strips the file extension, so scripts/run.py and scripts/run.sh both become the tool name 'run'. Skill scripts are exposed as tools by their base filename, so tool names must be unique within the package.

Solutions

  1. Rename one of the scripts so the base filenames differ, e.g. scripts/run.py and scripts/run-shell.sh.
  2. Remove the obsolete script variant before zipping.
  3. Keep exactly one language per logical tool name.

Example fix

# before: scripts/run.py and scripts/run.sh collide on tool name 'run'
# after: rename one
mv scripts/run.sh scripts/run-bash.sh
Defensive patterns

Strategy: validation

Validate before calling

// Before zipping, ensure script base filenames are unique
Set<String> toolNames = new HashSet<>();
for (Path p : scriptsDirFiles) {
    String fn = p.getFileName().toString();
    String tool = fn.contains(".") ? fn.substring(0, fn.lastIndexOf('.')) : fn;
    if (!toolNames.add(tool)) throw new IllegalStateException("duplicate tool name: " + tool);
}

Type guard

static boolean uniqueScriptToolNames(List<String> scriptFilenames) {
    Set<String> seen = new HashSet<>();
    for (String fn : scriptFilenames) {
        String tool = fn.contains(".") ? fn.substring(0, fn.lastIndexOf('.')) : fn;
        if (!seen.add(tool)) return false;
    }
    return true;
}

Try / catch

try { skillRegistryService.register(manifest, pkg); }
catch (IllegalArgumentException e) {
    if (e.getMessage().contains("duplicate script tool name")) { /* rename one script */ }
    else throw e;
}

Prevention

When it happens

Trigger: POST /api/skills/register with a zip containing scripts/run.py AND scripts/run.sh (or scripts/parse.js and scripts/parse.ts). The second one encountered triggers the duplicate detection.

Common situations: Providing both a Python and a shell variant of the same tool; porting a script to a new language and leaving the old one in scripts/; two scripts that differ only by extension.

Related errors


AI-assisted analysis of conductor-oss/conductor@cf7c3e4a8a (2026-08-14). Data as JSON: /api/errors/238ae56f51d43b04. Report an issue: GitHub.

Appendix: source

Thrown at agentspan/src/main/java/org/conductoross/conductor/ai/agentspan/runtime/service/SkillRegistryService.java:514

        Map<String, String> agentFiles = new LinkedHashMap<>();
        Map<String, Map<String, Object>> scripts = new LinkedHashMap<>();
        List<String> resourceFiles = new ArrayList<>();

        for (String path : contentByPath.keySet()) {
            if (path.equals("SKILL.md")) {
                continue;
            }
            if (isRootAgentFile(path)) {
                agentFiles.put(
                        path.substring(0, path.length() - "-agent.md".length()),
                        decodeUtf8(path, contentByPath.get(path)));
                continue;
            }
            if (isScriptFile(path)) {
                String filename = path.substring("scripts/".length());
                String toolName = scriptToolName(filename);
                if (scripts.containsKey(toolName)) {
                    throw new IllegalArgumentException(
                            "Skill package contains duplicate script tool name: " + toolName);
                }
                Map<String, Object> info = new LinkedHashMap<>();
                info.put("filename", filename);
                info.put("language", detectScriptLanguage(filename));
                scripts.put(toolName, info);
                continue;
            }
            if (isResourceFile(path)) {
                resourceFiles.add(path);
            }
        }

        Map<String, Object> rawConfig = new LinkedHashMap<>();
        rawConfig.put("model", resolveModel(manifest));
        rawConfig.put("agentModels", resolveAgentModels(manifest));
        rawConfig.put("skillMd", skillMd);
        rawConfig.put("agentFiles", agentFiles);

View on GitHub (pinned to cf7c3e4a8a)