{"record":{"id":"a1b60eedf6a0c968","repo":"mastra-ai/mastra","slug":"skillpath-is-missing-skill-md-resolvedpath","errorCode":null,"errorMessage":"skillPath is missing SKILL.md: ${resolvedPath}","messagePattern":"skillPath is missing SKILL\\.md: (.+?)","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"packages/server/src/server/handlers/stored-skills.ts","lineNumber":640,"sourceCode":"      try {\n        const stat = await fs.stat(resolvedPath);\n        if (!stat.isDirectory()) {\n          throw new HTTPException(400, { message: `skillPath is not a directory: ${resolvedPath}` });\n        }\n      } catch (err) {\n        if (err instanceof HTTPException) throw err;\n        if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') {\n          throw new HTTPException(400, {\n            message: `skillPath does not exist on the server filesystem: ${resolvedPath}. Create the skill directory (with a SKILL.md) before publishing, or use a skill that was materialized to disk.`,\n          });\n        }\n        throw err;\n      }\n      try {\n        await fs.stat(path.default.join(resolvedPath, 'SKILL.md'));\n      } catch (err) {\n        if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') {\n          throw new HTTPException(400, {\n            message: `skillPath is missing SKILL.md: ${resolvedPath}`,\n          });\n        }\n        throw err;\n      }\n\n      // Use LocalSkillSource to read from the server filesystem\n      const source = new LocalSkillSource();\n      const { publishSkillFromSource } = await import('@mastra/core/workspace');\n\n      const { snapshot, tree, files } = await publishSkillFromSource(source, resolvedPath, blobStore);\n\n      // Strip undefined keys from the snapshot before passing to update(). The\n      // storage layer treats \"field present\" as \"field changed\"; forwarding\n      // undefined would overwrite populated columns with undefined and trip\n      // NOT NULL / \"undefined cannot be passed as argument\" errors in\n      // adapters that bind args raw (libsql, pg).\n      const snapshotUpdate: Record<string, unknown> = {};","sourceCodeStart":622,"sourceCodeEnd":658,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/packages/server/src/server/handlers/stored-skills.ts#L622-L658","documentation":"The publish-stored-skill handler validates the source directory on the server filesystem before publishing. After confirming the path exists and is a directory, it checks for SKILL.md; if stat returns ENOENT it throws this 400. Mastra requires every skill directory to contain a SKILL.md manifest, so publishing without one is rejected up front instead of failing later inside publishSkillFromSource.","triggerScenarios":"POST to the publish stored skill route (/api/stored/skills/:id/publish) with a skillPath whose directory exists on the server but contains no SKILL.md file (typo like SKILL.MD or skill.md, file not yet created, wrong subdirectory).","commonSituations":"Pointing skillPath at a parent directory that only contains skill folders; generating skill files asynchronously and publishing before SKILL.md is written; case-sensitive filesystems where 'skill.md' does not match 'SKILL.md'; containers where only part of the skill directory was copied/mounted.","solutions":["Create a SKILL.md file inside the directory referenced by skillPath before publishing","Check the file name casing exactly matches SKILL.md (filesystems are case-sensitive on Linux)","Verify skillPath points at the skill folder itself, not its parent or a sibling","Ensure the file was fully written/synced (or volume mounted) before calling publish"],"exampleFix":"// before\nawait publishSkill({ id: skillId, skillPath: './skills/my-skill' }); // dir exists, no SKILL.md\n// after\nimport { writeFile } from 'node:fs/promises';\nawait writeFile('./skills/my-skill/SKILL.md', '---\nname: my-skill\ndescription: Does things\n---\n...');\nawait publishSkill({ id: skillId, skillPath: './skills/my-skill' });","handlingStrategy":"validation","validationCode":"import { stat } from 'node:fs/promises';\nimport path from 'node:path';\nexport async function assertPublishableSkillDir(skillPath: string): Promise<void> {\n  const resolved = path.resolve(skillPath);\n  const s = await stat(resolved);\n  if (!s.isDirectory()) throw new Error(`Not a directory: ${resolved}`);\n  await stat(path.join(resolved, 'SKILL.md')); // throws ENOENT if missing\n}","typeGuard":null,"tryCatchPattern":"try {\n  await publishSkill({ id, skillPath });\n} catch (e) {\n  if (e instanceof HTTPException && e.status === 400 && String(e.message).includes('missing SKILL.md')) {\n    console.error(`Add SKILL.md to ${skillPath} (exact casing) before publishing`);\n  } else throw e;\n}","preventionTips":["Always scaffold skill dirs from a template that includes SKILL.md","Check exact filename casing 'SKILL.md' on case-sensitive filesystems","Point skillPath at the skill folder, never its parent","Wait for file writes/mounts to complete before publishing"],"tags":["filesystem","validation","skills","http-400"],"backgroundTag":"missing-required-file","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}