siyuan-note/siyuan · error
template destination already exists or is inaccessible
Error message
template destination already exists or is inaccessible
What it means
ManageTemplateFiles is the kernel API for managing files in the templates workspace (data/templates). When the requested action is a move/rename, the kernel refuses if the destination path already exists on disk or cannot be Stat'ed for another reason (permissions, dangling entry). This guard prevents silently overwriting an existing template file or directory.
Source
Thrown at kernel/model/template_manage.go:306
return nil, errors.New("cannot write a template directory")
}
err = writeTemplateSource(root, request.Path, request.Content, info == nil)
return map[string]string{"revision": fmt.Sprintf("%x", sha256.Sum256([]byte(request.Content)))}, err
case "move":
if info.IsDir() && isManagedTemplatePackage(root, request.Path) {
return nil, errors.New("template packages cannot be renamed or moved")
}
if err = checkTemplateFilePath(root, request.Target); err != nil {
return nil, err
}
if err = validateNewTemplateName(request.Target); err != nil {
return nil, err
}
if !info.IsDir() && !strings.EqualFold(path.Ext(request.Target), ".md") {
return nil, errors.New("template source must use the .md extension")
}
if _, err = root.Stat(request.Target); !errors.Is(err, os.ErrNotExist) {
return nil, errors.New("template destination already exists or is inaccessible")
}
return nil, root.Rename(request.Path, request.Target)
case "remove":
// 将整个目录连同包资源移入隐藏恢复目录,保留误删后的恢复材料。
trash := ".trash/" + ast.NewNodeID()
if err = root.MkdirAll(trash, 0700); err != nil {
return nil, err
}
target := path.Join(trash, path.Base(request.Path))
if err = root.Rename(request.Path, target); err != nil {
return nil, err
}
return map[string]string{"recoveryPath": target}, nil
default:
return nil, errors.New("unsupported template operation")
}
}
View on GitHub (pinned to 8641553a1f)
Solutions
- Pick a unique request.Target name (e.g. append a timestamp or -2 suffix) that does not exist in data/templates
- List existing templates first (action="list") and check the target name client-side before issuing the move
- Fix filesystem permissions on data/templates so the destination can be stat'ed and inspected
- Delete or rename the existing template at the target path first, then retry the move
Example fix
// before: blind rename, fails when target exists
await fetchPost('/api/template/manageTemplateFiles', {action: 'move', path: '/old.md', target: '/tpl.md'});
// after: ensure target is free
const entries = (await fetchPost('/api/template/manageTemplateFiles', {action: 'list'})).data;
if (!entries.some(e => e.path === '/tpl.md')) {
await fetchPost('/api/template/manageTemplateFiles', {action: 'move', path: '/old.md', target: '/tpl.md'});
} Defensive patterns
Strategy: validation
Validate before calling
const list = (await fetchPost('/api/template/manageTemplateFiles', {action: 'list'})).data;
if (list.some(e => e.path.toLowerCase() === target.toLowerCase())) {
throw new Error('target already exists: ' + target);
} Type guard
const targetIsFree = (entries, target) => !entries.some(e => e.path === target);
Try / catch
try {
await fetchPost('/api/template/manageTemplateFiles', {action: 'move', path, target});
} catch (e) {
if (String(e.message).includes('destination already exists')) {
target = uniqueName(target); // e.g. tpl-2.md
await fetchPost('/api/template/manageTemplateFiles', {action: 'move', path, target});
} else { throw e; }
} Prevention
- Always list existing templates before choosing a target name
- Generate target names with a uniqueness suffix (timestamp or counter)
- Treat names case-insensitively to stay safe on case-insensitive filesystems
When it happens
Trigger: Calling ManageTemplateFiles with action="move" (or rename semantics) where request.Target points to a path that already exists inside data/templates, or where root.Stat(request.Target) returns an error other than os.ErrNotExist (e.g. permission problem).
Common situations: User or plugin picks a target name that already exists in the template panel; concurrent rename into the same target from two clients; a stale file with odd permissions makes Stat fail; case-insensitive filesystems where the new name differs only in case from an existing template.
Understand the failure class
Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.
Related errors
- load tree failed: %s
- install local marketplace package failed: %w; rollback faile
- Query notebook failed
- Create notebook [%s] folder [%s] failed: %s
- Move notebook [%s] file [%s] failed: %s
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/300d69fea5332f81.
Report an issue: GitHub.