alibaba/nacos · error · IllegalArgumentException

Skill cannot be null

Error message

Skill cannot be null

What it means

SkillUtils.toZipBytes throws IllegalArgumentException when the Skill object itself is null. The method needs a non-null Skill to read its name, metadata, and resources for ZIP assembly.

Source

Thrown at api/src/main/java/com/alibaba/nacos/api/ai/model/skills/SkillUtils.java:112

        
        return skill.getSkillMd() == null ? EMPTY_STRING : skill.getSkillMd();
    }
    
    /**
     * Convert Skill object to a ZIP byte array containing all skill files.
     *
     * <p>The ZIP structure mirrors the upload format:
     * {@code skillName/SKILL.md}, {@code skillName/type/resourceName}, etc.
     * Binary resources (marked with metadata encoding=base64) are decoded back to raw bytes.</p>
     *
     * @param skill the Skill object to convert
     * @return ZIP file as byte array
     * @throws IOException if ZIP creation fails
     * @throws IllegalArgumentException if skill is null or skill name is blank
     */
    public static byte[] toZipBytes(Skill skill) throws IOException {
        if (skill == null) {
            throw new IllegalArgumentException("Skill cannot be null");
        }
        if (StringUtils.isBlank(skill.getName())) {
            throw new IllegalArgumentException("Skill name cannot be blank");
        }
        
        String skillName = skill.getName();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try (ZipOutputStream zos = new ZipOutputStream(baos)) {
            // 1. SKILL.md
            zos.putNextEntry(new ZipEntry(skillName + "/SKILL.md"));
            zos.write(toMarkdown(skill).getBytes(StandardCharsets.UTF_8));
            zos.closeEntry();
            
            // 2. Resource files
            if (skill.getResource() != null && !skill.getResource().isEmpty()) {
                for (SkillResource resource : skill.getResource().values()) {
                    if (resource == null || StringUtils.isBlank(resource.getName())) {
                        continue;

View on GitHub (pinned to 9b989acdf1)

Solutions

  1. Null-check the Skill object before calling toZipBytes.
  2. Return a 'not found' error to the caller if the skill lookup yields null.
  3. Use Optional.ofNullable(skill).orElseThrow() to make the null check explicit.

Example fix

// before
byte[] zip = SkillUtils.toZipBytes(skillService.find(name)); // may be null

// after
Skill skill = skillService.find(name);
if (skill == null) {
    throw new NoSuchElementException("Skill not found: " + name);
}
byte[] zip = SkillUtils.toZipBytes(skill);
Defensive patterns

Strategy: validation

Validate before calling

if (skill == null) {
    throw new NoSuchElementException("Skill not found");
}
byte[] zip = SkillUtils.toZipBytes(skill);

Type guard

public static boolean isSkillReadyForZip(Skill skill) {
    return skill != null && skill.getName() != null && !skill.getName().trim().isEmpty();
}

Try / catch

try {
    byte[] zip = SkillUtils.toZipBytes(skill);
} catch (IllegalArgumentException e) {
    if (e.getMessage().contains("Skill cannot be null")) {
        return notFound("Skill not found");
    }
    throw e;
}

Prevention

When it happens

Trigger: Calling SkillUtils.toZipBytes(null). Happens when a skill lookup returns null (not found) and the result is passed directly without a null check. Also in stream pipelines where a filter or map produces null.

Common situations: A skill download/upload flow receives null because the skill was not found in storage. A deserialized Skill object is null due to a missing JSON body. A test passes null accidentally.

Related errors


AI-assisted analysis of alibaba/nacos@9b989acdf1 (2026-08-14). Data as JSON: /api/errors/5ba3bc48932253bf. Report an issue: GitHub.