mastra-ai/mastra · error · HTTPException

skillPath is missing SKILL.md: ${resolvedPath}

Error message

skillPath is missing SKILL.md: ${resolvedPath}

What it means

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.

Source

Thrown at packages/server/src/server/handlers/stored-skills.ts:640

      try {
        const stat = await fs.stat(resolvedPath);
        if (!stat.isDirectory()) {
          throw new HTTPException(400, { message: `skillPath is not a directory: ${resolvedPath}` });
        }
      } catch (err) {
        if (err instanceof HTTPException) throw err;
        if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') {
          throw new HTTPException(400, {
            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.`,
          });
        }
        throw err;
      }
      try {
        await fs.stat(path.default.join(resolvedPath, 'SKILL.md'));
      } catch (err) {
        if ((err as NodeJS.ErrnoException)?.code === 'ENOENT') {
          throw new HTTPException(400, {
            message: `skillPath is missing SKILL.md: ${resolvedPath}`,
          });
        }
        throw err;
      }

      // Use LocalSkillSource to read from the server filesystem
      const source = new LocalSkillSource();
      const { publishSkillFromSource } = await import('@mastra/core/workspace');

      const { snapshot, tree, files } = await publishSkillFromSource(source, resolvedPath, blobStore);

      // Strip undefined keys from the snapshot before passing to update(). The
      // storage layer treats "field present" as "field changed"; forwarding
      // undefined would overwrite populated columns with undefined and trip
      // NOT NULL / "undefined cannot be passed as argument" errors in
      // adapters that bind args raw (libsql, pg).
      const snapshotUpdate: Record<string, unknown> = {};

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Create a SKILL.md file inside the directory referenced by skillPath before publishing
  2. Check the file name casing exactly matches SKILL.md (filesystems are case-sensitive on Linux)
  3. Verify skillPath points at the skill folder itself, not its parent or a sibling
  4. Ensure the file was fully written/synced (or volume mounted) before calling publish

Example fix

// before
await publishSkill({ id: skillId, skillPath: './skills/my-skill' }); // dir exists, no SKILL.md
// after
import { writeFile } from 'node:fs/promises';
await writeFile('./skills/my-skill/SKILL.md', '---
name: my-skill
description: Does things
---
...');
await publishSkill({ id: skillId, skillPath: './skills/my-skill' });
Defensive patterns

Strategy: validation

Validate before calling

import { stat } from 'node:fs/promises';
import path from 'node:path';
export async function assertPublishableSkillDir(skillPath: string): Promise<void> {
  const resolved = path.resolve(skillPath);
  const s = await stat(resolved);
  if (!s.isDirectory()) throw new Error(`Not a directory: ${resolved}`);
  await stat(path.join(resolved, 'SKILL.md')); // throws ENOENT if missing
}

Try / catch

try {
  await publishSkill({ id, skillPath });
} catch (e) {
  if (e instanceof HTTPException && e.status === 400 && String(e.message).includes('missing SKILL.md')) {
    console.error(`Add SKILL.md to ${skillPath} (exact casing) before publishing`);
  } else throw e;
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/a1b60eedf6a0c968. Report an issue: GitHub.