siyuan-note/siyuan · error
reserved skill file name
Error message
reserved skill file name
What it means
To avoid Windows reserved device names (CON, PRN, AUX, NUL, CONIN$, CONOUT$), validateManagedSkillPath rejects any path component whose name before the first dot equals one of these (case-insensitive, after trimming trailing spaces/dots). This prevents files that would be created successfully on Unix but break or misbehave on Windows and in sync targets.
Solutions
- Rename the file to a non-reserved base name (e.g. 'console.md' instead of 'con.md')
- Avoid CON/PRN/AUX/NUL/CONIN$/CONOUT$ (case-insensitive) as file base names
- Sanitize generated filenames against the Windows reserved-name list before install
Example fix
// before path := "my-skill/con.md" // after path := "my-skill/console.md"
Defensive patterns
Strategy: validation
Validate before calling
const RESERVED = ['con','prn','aux','nul','conin$','conout$'];
function nameOk(part) {
return !RESERVED.includes(part.split('.')[0].replace(/[ .]+$/,'').toLowerCase());
} Try / catch
try {
await api.manageSkillFiles({action: 'create', path});
} catch (e) {
if (e.message === 'reserved skill file name') {
// pick a different base name and retry
}
} Prevention
- Maintain a reserved-name denylist in any file-naming UI
- Never name files CON/PRN/AUX/NUL even on macOS/Linux
- Validate names in plugin code before calling write/create
When it happens
Trigger: Creating, moving (renaming), or writing to a skill file named e.g. 'con.md', 'NUL', 'aux.txt', 'Conin$.sh' — the uppercase base name before '.' matches a reserved device.
Common situations: Test files named 'con' or 'nul' by convention; artifacts generated with device-ish names; zips authored on Linux containing such names.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/dc9d894b3e6ef4ae.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/util/skill_manage.go:68
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)
if err != nil {
return nil, err
}
defer workspace.Close()View on GitHub (pinned to 9f775e8a12)