{"record":{"id":"ab2315d9076efa29","repo":"agentscope-ai/agentscope","slug":"no-skill-md-at-the-root-of-root-r","errorCode":null,"errorMessage":"No SKILL.md at the root of {root!r}.","messagePattern":"No SKILL\\.md at the root of (.+?)\\.","errorType":"validation","errorClass":"SkillUploadError","httpStatus":null,"severity":"error","filePath":"src/agentscope/app/_service/_workspace.py","lineNumber":409,"sourceCode":"            if len(parts) < 2 or any(p in (\"\", \".\", \"..\") for p in parts):\n                raise SkillUploadError(f\"Unsafe upload path: {entry.path!r}\")\n            if entry.path.startswith(\"/\"):\n                raise SkillUploadError(f\"Unsafe upload path: {entry.path!r}\")\n            roots.add(parts[0])\n\n        if total > MAX_TOTAL_BYTES:\n            raise SkillUploadError(\n                f\"The upload is {total} bytes, over the \"\n                f\"{MAX_TOTAL_BYTES}-byte limit.\",\n            )\n        if len(roots) != 1:\n            raise SkillUploadError(\n                f\"A skill must be a single folder, got {sorted(roots)}.\",\n            )\n\n        root = roots.pop()\n        if not any(e.path == f\"{root}/SKILL.md\" for e in manifest.entries):\n            raise SkillUploadError(f\"No SKILL.md at the root of {root!r}.\")\n        return root\n\n    async def install_skill(\n        self,\n        workspace: WorkspaceBase,\n        stream: AsyncIterator[bytes],\n        archive_format: Literal[\"zip\", \"tar\", \"tar.gz\"],\n        name: str,\n        *,\n        agent_id: str | None = None,\n    ) -> None:\n        \"\"\"Pipe a skill archive into a workspace, one install at a time.\n\n        Takes the workspace rather than a session, because the\n        from-library path resolves once and then installs several.\n\n        Args:\n            workspace (`WorkspaceBase`): The target workspace.","sourceCodeStart":391,"sourceCodeEnd":427,"githubUrl":"https://github.com/agentscope-ai/agentscope/blob/e90f1c7592896cc95f6e5ee506194f533378247d/src/agentscope/app/_service/_workspace.py#L391-L427","documentation":"SkillUploadError raised at the end of validate_manifest: after establishing the single root folder, it requires an entry whose path is exactly f\"{root}/SKILL.md\". SKILL.md is the manifest that defines a skill, so an upload without it at the root is not a valid skill.","triggerScenarios":"The picked folder lacks SKILL.md at its top level — e.g. SKILL.md sits in a subfolder, is named skill.md/README.md, or the client filtered it out of the manifest.","commonSituations":"Wrong folder selected (parent or child of the actual skill root); case or naming mistakes (skill.md, SKILL.MD); .md files excluded by a client-side file-type filter; SKILL.md listed with an absolute or extra-prefixed path so it does not equal root/SKILL.md.","solutions":["Ensure SKILL.md exists exactly at the top level of the picked folder with that exact name and path root/SKILL.md.","Check client filters are not dropping SKILL.md from the entries list.","If SKILL.md is nested, restructure so the folder containing SKILL.md is the upload root."],"exampleFix":"# before\n# picking repo/ which contains my-skill/SKILL.md\n\n# after\n# pick repo/my-skill/ so entries include \"my-skill/SKILL.md\"","handlingStrategy":"validation","validationCode":"roots = {e.path.replace(\"\\\\\", \"/\").split(\"/\")[0] for e in manifest.entries}\nroot = next(iter(roots)) if len(roots) == 1 else None\nassert root and any(e.path == f\"{root}/SKILL.md\" for e in manifest.entries), \"Missing SKILL.md at root\"","typeGuard":"def has_skill_md(m: UploadManifest) -> bool:\n    roots = {e.path.replace(\"\\\\\", \"/\").split(\"/\")[0] for e in m.entries}\n    return len(roots) == 1 and any(e.path == f\"{next(iter(roots))}/SKILL.md\" for e in m.entries)","tryCatchPattern":null,"preventionTips":["Pick the folder that directly contains SKILL.md.","Use the exact filename SKILL.md and don't filter .md files out of the manifest."],"tags":["upload","skill-md","validation","skill"],"backgroundTag":"missing-required-file","analyzedSha":"e90f1c7592896cc95f6e5ee506194f533378247d","analyzedAt":"2026-08-28T18:24:12.087Z","schemaVersion":2},"datasetVersion":"2026-08-28T21:17:43.275Z"}