#24244·mastra

feat(workspace): expose a public API to validate SKILL.md against its directory before persisting

Author: panlijiCreated Sep 17, 2026Updated Sep 17, 2026
Labelseffort:lowimpact:mediumstatus: auto-triagedstatus: needs approval

Summary

Skill metadata is only validated when a workspace discovers/loads skills. There is no supported way for an application to run the same check before a skill file is persisted, and validateSkillMetadata is not reachable from any public entrypoint. Apps that let models or users create skills (chat file tools, shell, upload endpoints, sync jobs) therefore produce invalid skills silently, and only learn about them later, once per discovery pass, as an unstructured console.error.

Asking for: a small public, side-effect-free validation API, and ideally a structured way to report invalid skills instead of only logging them.

Current behavior (@mastra/[email protected])

  • Validation happens at load time only: #parseSkillFile calls #validateSkillMetadata(...), and when validateOnLoad (default true) rejects the file it throws Invalid skill metadata in <path>: ....
  • #discoverSkillsInPath processes each skill directory independently with Promise.allSettled; rejected entries are dropped from the catalog and reported as console.error("[WorkspaceSkills] Failed to load skill from <path>:", error.message).
  • Nothing validates at write time. LocalFilesystem, copyFile, shell commands, or any custom WorkspaceFilesystem provider can create skills/<dir>/SKILL.md with invalid frontmatter, and the framework accepts the write.

Consequence for an app: the same broken directory is re-reported on every rescan/refresh (no dedup), the skill is invisible to the agent, and the user is never told why. In our production deployment, a 15-minute window contained 173 skill-metadata error events across only 72 distinct paths - i.e. the same files being re-reported repeatedly, not a steady stream of new ones.

What we cannot do today

validateSkillMetadata(metadata, dirName, instructions) is declared in dist/workspace/skills/schemas.d.ts, but it is not exported from any public subpath:

  • @mastra/core/workspace does not re-export it
  • @mastra/core/workspace/skills is not in package.json#exports, so import('@mastra/core/workspace/skills') fails with ERR_PACKAGE_PATH_NOT_EXPORTED
  • @mastra/core/skills exports createSkill, InlineSkillSource, isInlineSkill, mergeWorkspaceSkills and resolveAgentSkills
  • the WorkspaceSkills interface offers addSkill? / removeSkill but no validate(...)

So the only supported way to reuse the framework's own rules is to construct a throwaway Workspace with a fake in-memory SkillSource, call workspace.skills.addSkill(dirName) to force a parse, and then workspace.destroy() - once per file write. It works, but it is a lot of machinery for "is this SKILL.md valid for this directory name?", and it pushes every app into reimplementing frontmatter parsing and the same wrapper.

Proposed API (any one of these would unblock us)

A. Export the pure validator (smallest change):

typescript
import { validateSkillMetadata } from '@mastra/core/skills';

Ideally alongside a content-level helper that parses frontmatter and returns both errors and parsed fields, so apps do not have to depend on the parser internals directly:

typescript
validateSkillContent(content: string, directoryName: string): SkillValidationResult

B. A first-class pre-write hook, for example Workspace({ skills: { validateOnWrite: true } }), or a documented extension point that custom WorkspaceFilesystem providers can call so writes to skills/<dir>/SKILL.md are checked with the same rules before they are persisted.

C. Structured reporting for invalid skills, instead of only console.error: for example an onSkillValidationError callback in the skills config, or workspace.skills.listInvalid() returning { path, errors }. This would let apps dedup logs, surface the reason to users and offer repair, while keeping the existing validateOnLoad fail-closed behavior intact.

Repro (current behavior)

typescript
const workspace = new Workspace({ skills: ['/tmp/ws/skills'] });
// write /tmp/ws/skills/foo/SKILL.md with frontmatter `name: bar`
// -> no error at write time
await workspace.skills.list();
// -> logs: Invalid skill metadata in ...: Skill name "bar" must match directory name "foo"

We are not asking to relax validation, and not asking for auto-repair: validateOnLoad staying fail-closed is the right default. We would just like to fail earlier, with a structured reason, using the framework's own rules.

Environment

@mastra/[email protected], Node 22, skills on a local filesystem behind a custom WorkspaceFilesystem provider.