{"record":{"id":"57f9bb8c2604956b","repo":"agentscope-ai/agentscope","slug":"a-skill-may-hold-at-most-max-file-count-files-g","errorCode":null,"errorMessage":"A skill may hold at most {MAX_FILE_COUNT} files, got {len(entries)}.","messagePattern":"A skill may hold at most (.+?) files, got (.+?)\\.","errorType":"exception","errorClass":"SkillUploadError","httpStatus":null,"severity":"error","filePath":"src/agentscope/app/_service/_workspace.py","lineNumber":375,"sourceCode":"\n        Returns:\n            `str`: The single top-level directory every path sits under.\n\n        Raises:\n            SkillUploadError: If a limit is exceeded, a path is unsafe,\n                the paths span more than one root, no ``SKILL.md`` is\n                present, or the parts do not match the manifest.\n        \"\"\"\n        entries = manifest.entries\n        if not entries:\n            raise SkillUploadError(\"The upload contains no files.\")\n        if file_count != len(entries):\n            raise SkillUploadError(\n                f\"The manifest lists {len(entries)} files but \"\n                f\"{file_count} were sent.\",\n            )\n        if len(entries) > MAX_FILE_COUNT:\n            raise SkillUploadError(\n                f\"A skill may hold at most {MAX_FILE_COUNT} files, \"\n                f\"got {len(entries)}.\",\n            )\n\n        total = 0\n        roots: set[str] = set()\n        for entry in entries:\n            if entry.size > MAX_FILE_BYTES:\n                raise SkillUploadError(\n                    f\"{entry.path!r} is {entry.size} bytes, over the \"\n                    f\"{MAX_FILE_BYTES}-byte per-file limit.\",\n                )\n            total += entry.size\n\n            parts = entry.path.replace(\"\\\\\", \"/\").split(\"/\")\n            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(\"/\"):","sourceCodeStart":357,"sourceCodeEnd":393,"githubUrl":"https://github.com/agentscope-ai/agentscope/blob/e90f1c7592896cc95f6e5ee506194f533378247d/src/agentscope/app/_service/_workspace.py#L357-L393","documentation":"SkillUploadError raised when len(manifest.entries) exceeds MAX_FILE_COUNT (100). The ceiling bounds sandbox disk usage and how long a concurrency slot is held during install, per the constants at the top of _workspace.py.","triggerScenarios":"Uploading a skill folder with more than 100 files — e.g. packaging node_modules, .git, virtualenvs, or build output inside the skill folder.","commonSituations":"No .gitignore-style exclusion on the client; picking a project root instead of the skill folder; generated artifacts or lockfile directories inflating the count.","solutions":["Exclude non-source directories (node_modules, .git, __pycache__, dist, .venv) from the pick/upload.","Zip or vendor large dependencies elsewhere and reference them by URL from SKILL.md instead of shipping them.","If the skill genuinely needs >100 files, split it into multiple skills or request a higher server-side MAX_FILE_COUNT."],"exampleFix":"# before\nentries = [e for e in all_entries]\n\n# after\nSKIP = {\"node_modules\", \".git\", \"__pycache__\", \".venv\", \"dist\"}\nentries = [e for e in all_entries if not any(p in SKIP for p in e.path.split(\"/\"))]\nassert len(entries) <= 100","handlingStrategy":"validation","validationCode":"MAX = 100\nassert len(manifest.entries) <= MAX, f\"{len(manifest.entries)} files; prune extras\"","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Exclude node_modules/.git/__pycache__/dist from folder picks.","Show a file count in the UI before submitting."],"tags":["upload","limit","file-count","skill"],"backgroundTag":"upload-limit-exceeded","analyzedSha":"e90f1c7592896cc95f6e5ee506194f533378247d","analyzedAt":"2026-08-28T18:24:12.087Z","schemaVersion":2},"datasetVersion":"2026-08-28T21:17:43.275Z"}