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
- 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
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
- 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
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
- Invalid skill name "${name}". Names must start with alphanum
- Skill name and reference path are required
- Malformed referencePath
- Invalid skill name "${name}". Names must start with alphanum
- bad request: ${responseText}
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/a1b60eedf6a0c968.
Report an issue: GitHub.