siyuan-note/siyuan · error
invalid skill path component
Error message
invalid skill path component
What it means
Each path component in a managed skill path must be filesystem-safe across platforms: no leading/trailing whitespace, no trailing dot, no characters in <>"|?*~, and no Unicode control runes. A component violating these per-segment rules produces 'invalid skill path component'.
Solutions
- Rename the target to remove the offending characters (trailing dots/spaces, <>"|?*~)
- Trim whitespace from each path segment before sending
- Replace control characters (e.g. from copy-paste) with safe characters
Example fix
// before path := "my skill /notes?.md" // after path := "my-skill/notes.md"
Defensive patterns
Strategy: validation
Validate before calling
function segmentOk(part) {
return part === part.trim() && !part.endsWith('.') &&
!/[<>"|?*~]/.test(part) && ![...part].some(c => c.charCodeAt(0) < 32 || c.charCodeAt(0) === 127);
}
const ok = path.split('/').every(segmentOk); Try / catch
try {
await api.manageSkillFiles({action: 'move', path, target});
} catch (e) {
if (e.message === 'invalid skill path component') {
// sanitize the offending segment and retry
}
} Prevention
- Sanitize filenames (trim, strip reserved chars) before any file API call
- Avoid copying filenames verbatim from clipboard/search output
- Test names for cross-platform safety
When it happens
Trigger: Path segments containing spaces at edges, a trailing dot ('file.'), wildcards ('*'), quotes, '~', '?', '<', '>', '|', or control characters anywhere in a segment passed to ManageSkillFiles.
Common situations: Filenames copied from search results with trailing spaces; names containing globs or template markers; files renamed on one OS with characters reserved on another.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- invalid content template path
- invalid history source path
- invalid skill path
- path contains invalid character
- reserved skill file name
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/2a4cee63545cd0a9.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/util/skill_manage.go:64
Editable bool
}
type SkillFileData struct {
Entries []SkillFileEntry
Content *string
Revision string
ReadOnlyReason string
}
// 管理操作使用真实的相对路径,不使用技能正文中的名称作为文件标识。
func validateManagedSkillPath(p string) error {
if p == "" || !fs.ValidPath(p) || strings.ContainsAny(p, "\\:") {
return errors.New("invalid skill path")
}
for _, part := range strings.Split(p, "/") {
if strings.TrimSpace(part) != part || strings.HasSuffix(part, ".") ||
strings.ContainsAny(part, "<>\"|?*~") || strings.ContainsFunc(part, unicode.IsControl) {
return errors.New("invalid skill path component")
}
device := strings.ToUpper(strings.TrimRight(strings.SplitN(part, ".", 2)[0], " ."))
if device == "CON" || device == "PRN" || device == "AUX" || device == "NUL" || device == "CONIN$" || device == "CONOUT$" {
return errors.New("reserved skill file name")
}
if strings.HasPrefix(device, "COM") || strings.HasPrefix(device, "LPT") {
number := strings.TrimPrefix(strings.TrimPrefix(device, "COM"), "LPT")
if len([]rune(number)) == 1 && strings.ContainsAny(number, "0123456789¹²³") {
return errors.New("reserved skill file name")
}
}
}
return nil
}
// 从工作空间目录逐层打开根目录,拒绝把管理操作重定向到链接目标。
func openManagedSkillsRoot() (*os.Root, error) {
workspace, err := os.OpenRoot(DataDir)View on GitHub (pinned to 9f775e8a12)