{"record":{"id":"78ebc1bc4c4c07ba","repo":"different-ai/openwork","slug":"agent-skill-name-must-match-its-parent-directory","errorCode":null,"errorMessage":"Agent Skill name must match its parent directory.","messagePattern":"Agent Skill name must match its parent directory\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"ee/apps/den-api/src/routes/org/plugin-system/store.ts","lineNumber":4682,"sourceCode":"  sourcePath: string\n}): GithubPluginSkillImportSkill {\n  const metadata = skillMetadataFromText(input.rawSourceText)\n  const base = {\n    description: metadata.description,\n    name: metadata.title,\n    pluginKey: input.plugin.key,\n    pluginName: input.plugin.displayName,\n    skillKey: githubPluginSkillKey({ pluginKey: input.plugin.key, sourcePath: input.sourcePath }),\n    sourceSchemaVersion: input.plugin.sourceKind === \"agent_plugin_manifest\" ? input.plugin.sourceSchemaVersion : null,\n    sourcePath: input.sourcePath,\n  }\n  if (input.plugin.sourceKind === \"agent_plugin_manifest\") {\n    try {\n      const projection = deriveSkillProjection({ rawSourceText: input.rawSourceText })\n      const pathSegments = input.sourcePath.split(\"/\").filter(Boolean)\n      const skillDirectoryName = pathSegments.at(-2) ?? \"\"\n      if (projection.title !== skillDirectoryName) {\n        throw new Error(\"Agent Skill name must match its parent directory.\")\n      }\n      return {\n        ...base,\n        description: projection.description,\n        name: projection.title,\n        rawSourceText: input.includeRawSourceText ? input.rawSourceText : undefined,\n        skippedReason: null,\n        supported: true,\n      }\n    } catch {\n      return {\n        ...base,\n        skippedReason: \"invalid_skill\",\n        supported: false,\n      }\n    }\n  }\n  if (!input.rawSourceText.trim() || !hasSkillFrontmatterName(input.rawSourceText)) {","sourceCodeStart":4664,"sourceCodeEnd":4700,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/ee/apps/den-api/src/routes/org/plugin-system/store.ts#L4664-L4700","documentation":"This is a marketplace/store validation in the OpenWork Den plugin system. When a plugin whose sourceKind is 'agent_plugin_manifest' is stored, the server derives the Agent Skill's title from the manifest's raw source text (frontmatter `name`) and compares it to the directory the file lives in (second-to-last path segment). If they differ, the upload is rejected because OpenCode-style Agent Skills require the skill name to equal its parent directory name.","triggerScenarios":"Saving/publishing a plugin with sourceKind 'agent_plugin_manifest' via the org plugin store where the skill frontmatter `name:` field does not exactly match the directory containing SKILL.md (pathSegments.at(-2)). E.g. a file at `skills/my-skill/SKILL.md` whose frontmatter says `name: My Skill` or `name: myskill`.","commonSituations":"Hand-writing SKILL.md frontmatter with spaces, capitalization, hyphens, or underscores that differ from the folder name; renaming a skill folder without updating frontmatter (or vice versa); generated skills scaffolded with a template name; copy-pasting a skill into a differently named directory.","solutions":["Rename the skill's parent directory to exactly match the frontmatter `name:` value (usually the easiest, since the manifest content is authoritative).","Alternatively, edit the SKILL.md frontmatter `name:` to exactly equal the parent directory name (case-sensitive, no spaces).","Re-check the sourcePath used for the upload: pathSegments.at(-2) must be the skill directory — ensure you are uploading the SKILL.md path, not a nested or flat path where .at(-2) resolves to the wrong segment.","Republish/re-save the plugin after the fix; the check runs on every store call."],"exampleFix":"// before (skills/Data-Analysis/SKILL.md)\n---\nname: data analysis\n---\n\n// after (directory renamed to data-analysis, frontmatter matches)\n---\nname: data-analysis\n---\n# data-analysis skill","handlingStrategy":"validation","validationCode":"function skillNameMatchesDirectory(sourcePath: string, rawSourceText: string): boolean {\n  const dir = sourcePath.split(\"/\").filter(Boolean).at(-2) ?? \"\"\n  const m = /^name:\\s*(.+)$/m.exec(rawSourceText)\n  return m !== null && m[1].trim() === dir\n}","typeGuard":"function hasValidSkillProjection(input: { sourcePath: string; rawSourceText: string }): boolean {\n  const dir = input.sourcePath.split(\"/\").filter(Boolean).at(-2)\n  return typeof dir === \"string\" && dir.length > 0 &&\n    deriveSkillProjection({ rawSourceText: input.rawSourceText }).title === dir\n}","tryCatchPattern":"try {\n  await storePlugin(input)\n} catch (e) {\n  if (e instanceof Error && e.message === \"Agent Skill name must match its parent directory.\") {\n    // surface: rename dir or fix frontmatter name\n  }\n}","preventionTips":["Scaffold skills from a template that derives `name:` from the directory name","Lint SKILL.md in CI: assert frontmatter name === parent directory","Avoid spaces/case changes in skill folder names","Re-check after renaming folders or editing frontmatter"],"tags":["validation","plugin-store","agent-skills","naming"],"backgroundTag":"skill-name-directory-mismatch","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}