feat(workspace): expose a public API to validate SKILL.md against its directory before persisting
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:
#parseSkillFilecalls#validateSkillMetadata(...), and whenvalidateOnLoad(defaulttrue) rejects the file it throwsInvalid skill metadata in <path>: .... #discoverSkillsInPathprocesses each skill directory independently withPromise.allSettled; rejected entries are dropped from the catalog and reported asconsole.error("[WorkspaceSkills] Failed to load skill from <path>:", error.message).- Nothing validates at write time.
LocalFilesystem,copyFile, shell commands, or any customWorkspaceFilesystemprovider can createskills/<dir>/SKILL.mdwith 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/workspacedoes not re-export it@mastra/core/workspace/skillsis not inpackage.json#exports, soimport('@mastra/core/workspace/skills')fails withERR_PACKAGE_PATH_NOT_EXPORTED@mastra/core/skillsexportscreateSkill,InlineSkillSource,isInlineSkill,mergeWorkspaceSkillsandresolveAgentSkills- the
WorkspaceSkillsinterface offersaddSkill?/removeSkillbut novalidate(...)
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):
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:
validateSkillContent(content: string, directoryName: string): SkillValidationResultB. 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)
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.
Source: mastra-ai/mastra