{"record":{"id":"eeb6d207f131cdc0","repo":"alibaba/nacos","slug":"20004-eeb6d2","errorCode":"20004","errorMessage":"Skill not found after deleting draft: {name}","messagePattern":"Skill not found after deleting draft: (.+?)","errorType":"exception","errorClass":"NacosApiException","httpStatus":404,"severity":"error","filePath":"ai/src/main/java/com/alibaba/nacos/ai/service/skills/SkillOperationServiceImpl.java","lineNumber":567,"sourceCode":"    private String deleteDraftAndCreateUploadedSkill(String namespaceId, Skill skill,\n        String targetVersion, AiResource meta, String commitMsg) throws NacosException {\n        String name = skill.getName();\n        if (meta == null) {\n            throw new NacosApiException(NacosException.CONFLICT, ErrorCode.RESOURCE_CONFLICT,\n                \"No editing draft to delete: \" + name);\n        }\n        checkWritableUploadResource(meta);\n        ResourceVersionInfo info = AiResourceManager.requireVersionInfo(meta);\n        ensureNoReviewingVersion(info, \"replace upload draft\");\n        if (StringUtils.isBlank(info.getEditingVersion())) {\n            throw new NacosApiException(NacosException.CONFLICT, ErrorCode.RESOURCE_CONFLICT,\n                \"No editing draft to delete: \" + name);\n        }\n        resourceManager.doDeleteDraft(namespaceId, name, RESOURCE_TYPE_SKILL,\n            v -> deleteSkillStorageForVersion(namespaceId, name, v.getVersion(), v.getStorage()));\n        AiResource latestMeta = resourceManager.findMeta(namespaceId, name, RESOURCE_TYPE_SKILL);\n        if (latestMeta == null) {\n            throw new NacosApiException(NacosException.NOT_FOUND, ErrorCode.RESOURCE_NOT_FOUND,\n                \"Skill not found after deleting draft: \" + name);\n        }\n        createDraftWithSkill(namespaceId, skill, targetVersion, latestMeta, false, commitMsg);\n        return name;\n    }\n    \n    private static void ensureNoReviewingVersion(ResourceVersionInfo info, String action)\n        throws NacosApiException {\n        if (info != null && StringUtils.isNotBlank(info.getReviewingVersion())) {\n            throw new NacosApiException(NacosException.CONFLICT, ErrorCode.RESOURCE_CONFLICT,\n                \"There is already a reviewing version: \" + info.getReviewingVersion()\n                    + \", cannot \" + action);\n        }\n    }\n    \n    /**\n     * Resolve the upload version for strict call sites.\n     * Priority: SKILL.md YAML front-matter (version / metadata.version) -> meta.json in ZIP -> user-specified targetVersion -> default \"0.0.1\".","sourceCodeStart":549,"sourceCodeEnd":585,"githubUrl":"https://github.com/alibaba/nacos/blob/9b989acdf181d00898f2e8839257bb2b2a3cefe3/ai/src/main/java/com/alibaba/nacos/ai/service/skills/SkillOperationServiceImpl.java#L549-L585","documentation":"A NOT_FOUND (HTTP 404, ErrorCode RESOURCE_NOT_FOUND/20004) from deleteDraftAndCreateUploadedSkill: after doDeleteDraft removed the editing draft, re-fetching the meta returned null — the entire skill was concurrently deleted between the two steps. The replace operation cannot continue because its target resource no longer exists.","triggerScenarios":"A concurrent deleteSkill (or namespace/resource removal) racing with a DELETE_DRAFT_AND_CREATE upload: the draft delete succeeds, then the meta lookup finds nothing.","commonSituations":"Two operators, one deleting the skill while another replaces its draft; automation deleting resources mid-upload; partial retry after an earlier failure.","solutions":["Treat the skill as gone; re-run precheck to confirm current state.","If the skill should still exist, recreate it with CREATE_DRAFT.","Prevent concurrent delete + replace on the same resource (serialize lifecycle ops)."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    skillOp.uploadSkillFromZip(req); // DELETE_DRAFT_AND_CREATE\n} catch (NacosApiException e) {\n    if (e.getErrCode() == NacosException.NOT_FOUND\n            && ErrorCode.RESOURCE_NOT_FOUND.getCode() == e.getDetailErrCode()\n            && e.getErrMsg().contains(\"after deleting draft\")) {\n        // resource was concurrently deleted: re-precheck and CREATE_DRAFT if it should exist\n        List<SkillUploadPrecheckResult> pc = skillOp.precheckUploadSkillFromZip(ns, zipBytes);\n        // ... decide from pc ...\n    } else {\n        throw e;\n    }\n}","preventionTips":["Avoid concurrent delete and upload/replace on the same resource.","After a delete-draft step, re-precheck before continuing a replace flow.","Treat 'not found after deleting draft' as 'resource gone — recreate if needed'."],"tags":["concurrency","not-found","delete","race","skill"],"backgroundTag":null,"analyzedSha":"9b989acdf181d00898f2e8839257bb2b2a3cefe3","analyzedAt":"2026-08-14T07:17:31.569Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}