siyuan-note/siyuan · error
reserved template file name
Error message
reserved template file name
What it means
validateNewTemplateName rejects file names whose base (before the first dot, uppercased) is a Windows reserved device name: CON, PRN, AUX, NUL, or COM1-9/LPT1-9. Such names cannot be created as files on Windows, so they are refused up front for cross-platform safety.
Source
Thrown at kernel/model/template_manage.go:76
return nil
}
if p == "" || !fs.ValidPath(p) || strings.ContainsAny(p, "\\:\x00") {
return errors.New("invalid template path")
}
for _, part := range strings.Split(p, "/") {
if strings.HasPrefix(part, ".") {
return errors.New("hidden template paths are reserved")
}
}
return nil
}
// 新名称保持跨平台可用,已有父目录沿用原名。
func validateNewTemplateName(p string) error {
part := path.Base(p)
device := strings.ToUpper(strings.SplitN(part, ".", 2)[0])
if device == "CON" || device == "PRN" || device == "AUX" || device == "NUL" || (len(device) == 4 && (strings.HasPrefix(device, "COM") || strings.HasPrefix(device, "LPT")) && device[3] >= '1' && device[3] <= '9') {
return errors.New("reserved template file name")
}
if strings.HasPrefix(part, ".") || strings.TrimSpace(part) != part || strings.HasSuffix(part, ".") || strings.ContainsAny(part, "\\:<>\"|?*") || strings.ContainsFunc(part, unicode.IsControl) {
return errors.New("invalid template path component")
}
return nil
}
// 清单与目录共同标识模板包,保留目录身份以维持搜索和集市更新。
func isManagedTemplatePackage(root *os.Root, p string) bool {
_, err := root.Lstat(path.Join(p, "template.json"))
return err == nil
}
func openTemplateRoot() (*os.Root, error) {
if err := os.MkdirAll(filepath.Join(util.DataDir, "templates"), 0755); err != nil {
return nil, err
}
return os.OpenRoot(filepath.Join(util.DataDir, "templates"))View on GitHub (pinned to 8641553a1f)
Solutions
- Choose a file name that is not a Windows reserved device name (append a word or suffix, e.g. "con-template.md")
- Validate user-supplied names in the UI before calling ManageTemplateFiles
- Add a name sanitizer that prefixes reserved names
Example fix
// before name := "con.md" err := validateNewTemplateName(name) // fails // after name := "con-template.md" err := validateNewTemplateName(name) // ok
Defensive patterns
Strategy: validation
Validate before calling
func isReservedDeviceName(name string) bool {
d := strings.ToUpper(strings.SplitN(path.Base(name), ".", 2)[0])
if d == "CON" || d == "PRN" || d == "AUX" || d == "NUL" { return true }
return len(d) == 4 && (strings.HasPrefix(d, "COM") || strings.HasPrefix(d, "LPT")) && d[3] >= '1' && d[3] <= '9'
} Try / catch
if isReservedDeviceName(newName) {
newName = "tpl-" + newName // sanitize before validating
}
if err := validateNewTemplateName(newName); err != nil { return err } Prevention
- Never name templates CON, PRN, AUX, NUL, COM1-9, or LPT1-9
- Sanitize user-supplied names against reserved devices in the UI
- Test name handling on Windows as well as Unix
When it happens
Trigger: ManageTemplateFiles calls validateNewTemplateName with a new template name whose path.Base matches a reserved device (case-insensitive), e.g. "con.md", "COM1.md", "lpt3.md".
Common situations: Users naming a template "con" or "aux" (works on Unix, breaks on Windows); automated generation of names from untrusted input; migration from a Unix-only setup to Windows.
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
- invalid template path component
- template source must use the .md extension
- Parse template failed: %s
- parse tree [%s] failed
- database [%s] template field [%s] rendering failed: %s
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/f71b798454a87c74.
Report an issue: GitHub.