siyuan-note/siyuan · error

cannot write a template directory

Error message

cannot write a template directory

What it means

The write action only accepts regular files. If the request.Path exists and is a directory (a plain template folder or a template package with template.json), the write is rejected because a directory cannot be replaced by file content — doing so would destroy package resources. This error indicates you aimed a file-write at a directory path.

Source

Thrown at kernel/model/template_manage.go:288

		if revisionErr != nil {
			return nil, revisionErr
		}
		if request.Revision == "" || request.Revision != revision {
			return nil, errors.New("template changed; reload it before saving, moving or deleting")
		}
	}
	switch request.Action {
	case "write":
		if info == nil {
			if err = validateNewTemplateName(request.Path); err != nil {
				return nil, err
			}
		}
		if len(request.Content) > maxTemplateSourceSize {
			return nil, errors.New("template source is too large")
		}
		if info != nil && info.IsDir() {
			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")

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Write to a different filename with a .md extension that does not collide with an existing directory
  2. If you intended to update a file inside the package, target the inner file, e.g. "my-pack/template.md"
  3. Remove or rename the existing directory first if it is truly unwanted (action="remove" or action="move")

Example fix

// before
{ "action": "write", "path": "my-pack", "revision": rev, "content": "text" }   // my-pack/ is a package dir
// after
{ "action": "write", "path": "my-pack/template.md", "revision": innerRev, "content": "text" }
Defensive patterns

Strategy: validation

Validate before calling

const entry = (await manageTemplateFiles({ action: 'list' })).find(e => e.path === p);
if (entry?.isDir) {
  throw new Error(`'${p}' is a directory; write to a file inside it instead`);
}

Try / catch

try {
  await manageTemplateFiles({ action: 'write', path: p, revision: rev, content });
} catch (e) {
  if (String(e.message).includes('cannot write a template directory')) {
    // redirect the write to p + '/template.md' or another non-colliding name
  }
}

Prevention

When it happens

Trigger: ManageTemplateFiles with action="write" where request.Path already exists as a directory — e.g. writing to "my-pack" when data/templates/my-pack/ is a template package, or a path collision where a directory was created earlier with action="mkdir" under the same name.

Common situations: A name collision: a template package directory already occupies the name the client wants for a new .md file; the client built the path without the .md suffix so it accidentally matched a directory; automation created directories and files inconsistently.

Related errors


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/a94091d754f41528. Report an issue: GitHub.