{"record":{"id":"994ab0218474c4ba","repo":"Significant-Gravitas/AutoGPT","slug":"file-is-not-a-valid-skill-md-expected-yaml-front","errorCode":null,"errorMessage":"File is not a valid SKILL.md — expected YAML frontmatter with 'name' and 'description' followed by a markdown body.","messagePattern":"File is not a valid SKILL\\.md — expected YAML frontmatter with 'name' and 'description' followed by a markdown body\\.","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"autogpt_platform/backend/backend/api/features/v1.py","lineNumber":2675,"sourceCode":"        409: {\"description\": \"Per-user skill limit reached\"},\n    },\n    dependencies=[Security(requires_user)],\n)\nasync def upload_copilot_skill(\n    user_id: Annotated[str, Security(get_user_id)],\n    body: UploadCopilotSkillRequest,\n) -> CopilotSkillInfo:\n    \"\"\"Create a user-distilled skill from an uploaded ``SKILL.md`` file.\n\n    Parses the canonical frontmatter + body, then reuses\n    :func:`backend.copilot.tools.skills.store_user_skill` so an uploaded skill\n    is validated, capped, and persisted exactly like one the copilot distils\n    via ``store_skill``.  Malformed files return 400, the per-user cap returns\n    409, and an existing slug is overwritten (upsert).\n    \"\"\"\n    parsed = parse_skill_markdown(body.content)\n    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:","sourceCodeStart":2657,"sourceCodeEnd":2693,"githubUrl":"https://github.com/Significant-Gravitas/AutoGPT/blob/9c8bb5550f446ba5d3046b78896578742495b3cf/autogpt_platform/backend/backend/api/features/v1.py#L2657-L2693","documentation":"Skill-upload endpoint returns 400 when `parse_skill_markdown(body.content)` returns None: the uploaded SKILL.md text does not match the canonical format — YAML frontmatter containing 'name' and 'description' followed by a markdown body. This is a client-side content-validation failure, not a server fault.","triggerScenarios":"POST upload of a SKILL.md whose frontmatter delimiters (---) are missing/unbalanced, whose YAML is invalid, whose frontmatter lacks name or description, or which has no markdown body after the frontmatter.","commonSituations":"Hand-edited skill files with tabs or unquoted colons breaking YAML; converting arbitrary .md files to 'skills' without adding frontmatter; copy-paste that drops the closing --- fence; uploading the file's binary bytes instead of decoded text.","solutions":["Start the file with a --- fence, include name: and description: in the frontmatter, close with ---, then add at least one line of markdown body.","Validate the YAML block with a linter (yamllint / an editor YAML mode) before upload.","Use a known-good default skill file as the template and edit from there."],"exampleFix":"# before (invalid — no frontmatter)\n# My Skill\nDoes stuff.\n\n# after\n---\nname: my-skill\ndescription: Does stuff on demand.\n---\n# My Skill\nRuns when the user asks for stuff.","handlingStrategy":"validation","validationCode":"function validateSkillMarkdown(content: string): string | null {\n  const m = content.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n([\\s\\S]*)$/);\n  if (!m) return 'Missing --- frontmatter fences';\n  try {\n    const fm = parseYaml(m[1]);\n    if (!fm.name || !fm.description) return \"Frontmatter needs 'name' and 'description'\";\n  } catch { return 'Frontmatter is not valid YAML'; }\n  if (!m[2].trim()) return 'Body is empty';\n  return null;\n}","typeGuard":"function looksLikeSkillMarkdown(content: string): boolean {\n  return /^---\\r?\\n[\\s\\S]*?\\r?\\n---\\r?\\n[\\s\\S]+/.test(content);\n}","tryCatchPattern":"try {\n  await api.uploadSkill(content);\n} catch (e) {\n  if (e.status === 400 && /valid SKILL\\.md/.test(e.detail)) { showFormatHelp(); return; }\n  throw e;\n}","preventionTips":["Validate the frontmatter structure client-side before uploading.","Use the official/default SKILL.md template as a starting point.","Run YAML linting on edited frontmatter."],"tags":["http-400","skill-markdown","yaml-frontmatter","validation"],"backgroundTag":null,"analyzedSha":"9c8bb5550f446ba5d3046b78896578742495b3cf","analyzedAt":"2026-08-14T17:17:21.957Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}