{"record":{"id":"47e5a8512b9726c5","repo":"Significant-Gravitas/AutoGPT","slug":"skill-limit-reached-max-user-skills-delete-an","errorCode":null,"errorMessage":"Skill limit reached ({MAX_USER_SKILLS}). Delete an unused skill first.","messagePattern":"Skill limit reached \\((.+?)\\)\\. Delete an unused skill first\\.","errorType":"http","errorClass":"HTTPException","httpStatus":409,"severity":"warning","filePath":"autogpt_platform/backend/backend/api/features/v1.py","lineNumber":2692,"sourceCode":"    if parsed is None:\n        raise HTTPException(\n            status_code=400,\n            detail=(\n                \"File is not a valid SKILL.md — expected YAML frontmatter with \"\n                \"'name' and 'description' followed by a markdown body.\"\n            ),\n        )\n    try:\n        stored = await store_user_skill(\n            user_id,\n            name=parsed.name,\n            description=parsed.description,\n            body=parsed.body,\n            triggers=list(parsed.triggers),\n            version=parsed.version,\n        )\n    except SkillLimitError as exc:\n        raise HTTPException(status_code=409, detail=str(exc))\n    except (VirusDetectedError, VirusScanError) as exc:\n        logger.warning(\"[skills] virus scan rejected uploaded skill: %s\", exc)\n        raise HTTPException(\n            status_code=400, detail=\"Skill content rejected by virus scan\"\n        )\n    except ValueError as exc:\n        raise HTTPException(status_code=400, detail=str(exc))\n    return CopilotSkillInfo(\n        name=stored.name,\n        description=stored.description,\n        triggers=list(stored.triggers),\n    )\n\n\n@v1_router.get(\n    path=\"/skills/{name}\",\n    summary=\"Read a single copilot skill with its full SKILL.md body\",\n    operation_id=\"readCopilotSkill\",","sourceCodeStart":2674,"sourceCodeEnd":2710,"githubUrl":"https://github.com/Significant-Gravitas/AutoGPT/blob/9c8bb5550f446ba5d3046b78896578742495b3cf/autogpt_platform/backend/backend/api/features/v1.py#L2674-L2710","documentation":"Skill-upload returns 409 when `store_user_skill` raises SkillLimitError: the authenticated user already has MAX_USER_SKILLS user-distilled skills stored. The cap is per-user and counted before the upsert, so even overwriting an existing slug counts against the limit path when the cap is already reached.","triggerScenarios":"POST upload of a new SKILL.md by a user whose stored skill count equals MAX_USER_SKILLS (the message interpolates the numeric cap, e.g. 'Skill limit reached (25)').","commonSituations":"Power users accumulating distilled copilot skills; automated pipelines bulk-uploading skills per user; test accounts that repeatedly upload unique-named skills.","solutions":["Delete one or more existing skills (DELETE /skills/{name}) then retry the upload.","If updating an existing skill, reuse its exact name so the upsert path applies rather than adding a new entry.","Operators: raise MAX_USER_SKILLS if the product allows, but treat it as a resource cap to respect."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"const skills = await api.listSkills();\nif (skills.length >= MAX_USER_SKILLS) {\n  show('Skill limit reached — delete an unused skill first');\n  return;\n}","typeGuard":null,"tryCatchPattern":"try {\n  await api.uploadSkill(content);\n} catch (e) {\n  if (e.status === 409) { promptSkillDeletion(); return; }\n  throw e;\n}","preventionTips":["Show remaining skill quota in the UI before upload attempts.","Reuse existing skill names to update via upsert instead of accumulating new entries.","Bulk-upload pipelines should check the cap and handle 409 explicitly."],"tags":["http-409","rate-limit","skills","quota"],"backgroundTag":null,"analyzedSha":"9c8bb5550f446ba5d3046b78896578742495b3cf","analyzedAt":"2026-08-14T17:17:21.957Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}