Describe the Bug
WorkspaceSkills in @mastra/core hardcodes '/' as the path separator in five places, so absolute Windows paths (e.g. C:\Users\me\skills\my-skill) passed via new Workspace({ skills: [...] }) are mishandled on Windows. Each call site fails differently:
| Line |
Method |
Code |
Effect on Windows |
| 316 |
addSkill |
this.#getParentPath(skillPath).split('/').pop() || 'unknown' |
dirName is 'unknown' (#getParentPath uses lastIndexOf('/') and returns '/' on a pure-backslash path → '/'.split('/').pop() === '' → fallback) |
| 319 |
addSkill |
skillPath.split('/').pop() || 'unknown' |
dirName is the entire path string (no / found, .split('/').pop() returns the whole input) |
| 773 |
#discoverDirectSkill |
skillDir.split('/').pop() || skillDir |
same — dirName is the entire path string |
| 791 |
#discoverDirectSkill |
skillsPath.split('/').pop() || skillsPath |
same |
| 1217 |
#determineSource |
const segments = skillsPath.split('/'); if (segments.includes('node_modules')) ... |
node_modules segment never detected on a pure-backslash path → external skills mis-classified as 'local' |
These are all in packages/core/src/workspace/skills/workspace-skills.ts. Same bug present on main and on every published 1.x (verified against 1.36.0 and 1.41.0).
Steps To Reproduce
The bug is pure string handling — the inputs are Windows-shaped path strings, so the four failures can be observed on any OS without a Windows machine. The repro transcribes the offending functions verbatim from upstream and runs them against three realistic Windows path inputs.
git clone https://gist.github.com/stephen-carter-at-sf/d6a6303218278b64181c9cc8fb95ae26 mastra-skill-windows-bug
cd mastra-skill-windows-bug
node repro.mjs
Output (current behavior, exit code 1):
FAIL: addSkill dirName (directory path)
expected: "my-skill"
actual: "C:\\Users\\me\\skills\\my-skill"
FAIL: addSkill dirName (SKILL.md path)
expected: "my-skill"
actual: "unknown"
FAIL: #discoverDirectSkill dirName (directory case)
expected: "my-skill"
actual: "C:\\Users\\me\\skills\\my-skill"
FAIL: #determineSource node_modules detection
expected: "external"
actual: "local"
4 of 4 cases fail — see workspace-skills.ts lines 316, 319, 773, 791, 1217.
In a real Mastra agent on Windows, the user-visible symptom is: skills configured by absolute path either fail to load or load with the wrong name (the entire path string, or 'unknown'), so Skill.get('my-skill') returns nothing and search/load tools can't find them.
Link to Minimal Reproducible Example
https://gist.github.com/stephen-carter-at-sf/d6a6303218278b64181c9cc8fb95ae26
Expected Behavior
new Workspace({ skills: ['C:\\Users\\me\\skills\\my-skill'] }) on Windows should discover the skill with name = 'my-skill', identical to the macOS/Linux behavior with '/Users/me/skills/my-skill'. #determineSource should classify a path containing node_modules as 'external' regardless of separator.
Suggested fix
Workspace-internal paths can stay POSIX (the docstring on #joinPath already notes "workspace paths use forward slashes"). The only places that need separator-tolerance are the five entry-point lines above where consumer-supplied absolute paths land. A small helper covers all five:
function splitPathSegments(p: string): string[] {
return p.split(/[\\/]+/);
}
…and #getParentPath should use Math.max(p.lastIndexOf('/'), p.lastIndexOf('\\')) instead of just '/'.
Happy to send a PR if that helps.
Environment Information
Bug is in source — reproducible on any OS by running the script above against published `@mastra/core@1.41.0` and the current `main` branch. No environment-specific observation needed; pasting `npx envinfo` output isn't load-bearing for a string-handling bug, but for completeness:
System: macOS 25.5.0
@mastra/core versions verified: 1.36.0, 1.41.0, main (HEAD)
LLM Provider: N/A (the failing code path runs before any model is invoked)
Verification
Describe the Bug
WorkspaceSkillsin@mastra/corehardcodes'/'as the path separator in five places, so absolute Windows paths (e.g.C:\Users\me\skills\my-skill) passed vianew Workspace({ skills: [...] })are mishandled on Windows. Each call site fails differently:addSkillthis.#getParentPath(skillPath).split('/').pop() || 'unknown'dirNameis'unknown'(#getParentPathuseslastIndexOf('/')and returns'/'on a pure-backslash path →'/'.split('/').pop() === ''→ fallback)addSkillskillPath.split('/').pop() || 'unknown'dirNameis the entire path string (no/found,.split('/').pop()returns the whole input)#discoverDirectSkillskillDir.split('/').pop() || skillDirdirNameis the entire path string#discoverDirectSkillskillsPath.split('/').pop() || skillsPath#determineSourceconst segments = skillsPath.split('/'); if (segments.includes('node_modules')) ...node_modulessegment never detected on a pure-backslash path → external skills mis-classified as'local'These are all in
packages/core/src/workspace/skills/workspace-skills.ts. Same bug present onmainand on every published1.x(verified against1.36.0and1.41.0).Steps To Reproduce
The bug is pure string handling — the inputs are Windows-shaped path strings, so the four failures can be observed on any OS without a Windows machine. The repro transcribes the offending functions verbatim from upstream and runs them against three realistic Windows path inputs.
git clone https://gist.github.com/stephen-carter-at-sf/d6a6303218278b64181c9cc8fb95ae26 mastra-skill-windows-bug cd mastra-skill-windows-bug node repro.mjsOutput (current behavior, exit code 1):
In a real Mastra agent on Windows, the user-visible symptom is: skills configured by absolute path either fail to load or load with the wrong
name(the entire path string, or'unknown'), soSkill.get('my-skill')returns nothing and search/load tools can't find them.Link to Minimal Reproducible Example
https://gist.github.com/stephen-carter-at-sf/d6a6303218278b64181c9cc8fb95ae26
Expected Behavior
new Workspace({ skills: ['C:\\Users\\me\\skills\\my-skill'] })on Windows should discover the skill withname = 'my-skill', identical to the macOS/Linux behavior with'/Users/me/skills/my-skill'.#determineSourceshould classify a path containingnode_modulesas'external'regardless of separator.Suggested fix
Workspace-internal paths can stay POSIX (the docstring on
#joinPathalready notes "workspace paths use forward slashes"). The only places that need separator-tolerance are the five entry-point lines above where consumer-supplied absolute paths land. A small helper covers all five:…and
#getParentPathshould useMath.max(p.lastIndexOf('/'), p.lastIndexOf('\\'))instead of just'/'.Happy to send a PR if that helps.
Environment Information
Verification