siyuan-note/siyuan · error

template changed; reload it before saving, moving or deletin

Error message

template changed; reload it before saving, moving or deleting

What it means

ManageTemplateFiles implements optimistic concurrency: for any existing path (info != nil) it recomputes a SHA-256 revision of the file/directory and requires the caller to pass that exact value in request.Revision before allowing write, move, or remove. If the revision is missing or stale — meaning someone else modified the template since the caller last read it — the operation is rejected so conflicting edits do not silently overwrite each other.

Source

Thrown at kernel/model/template_manage.go:274

		if !strings.EqualFold(path.Ext(request.Path), ".md") {
			return nil, errors.New("template source must use the .md extension")
		}
	}
	if request.Action == "read" {
		if info.IsDir() {
			revision, readErr := templateFileRevision(root, request.Path)
			return map[string]string{"content": "", "revision": revision}, readErr
		}
		content, readErr := readTemplateSource(root, request.Path)
		return map[string]string{"content": content, "revision": fmt.Sprintf("%x", sha256.Sum256([]byte(content))), "path": filepath.Join(util.DataDir, "templates", filepath.FromSlash(request.Path))}, readErr
	}
	if info != nil {
		revision, revisionErr := templateFileRevision(root, request.Path)
		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":

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Re-read the template (action="read") to get the fresh revision and your desired content, then retry the write/move/remove with the new Revision
  2. If your change should win, merge your edits into the freshly read content before re-saving — do not blindly retry with the old content
  3. Check what else is touching data/templates (sync clients, other SiYuan windows) and avoid concurrent edits

Example fix

// before (stale revision)
{ "action": "write", "path": "t.md", "revision": "abc123...", "content": "new text" }
// after: first
{ "action": "read", "path": "t.md" }  -> revision "def456..."
// then
{ "action": "write", "path": "t.md", "revision": "def456...", "content": "merged text" }
Defensive patterns

Strategy: retry

Validate before calling

// always carry the revision from your last read into any mutating call
const { revision } = await manageTemplateFiles({ action: 'read', path: p });
await manageTemplateFiles({ action: 'write', path: p, revision, content });

Try / catch

try {
  await saveTemplate(p, content, cachedRev);
} catch (e) {
  if (String(e.message).includes('reload it before')) {
    const fresh = await manageTemplateFiles({ action: 'read', path: p });
    const merged = mergeEdits(fresh.content, content); // never blindly overwrite
    await saveTemplate(p, merged, fresh.revision);
  }
}

Prevention

When it happens

Trigger: Any write/move/remove on an existing template where request.Revision is empty, or differs from the current hash: another window/tab saved the template in between, a sync service touched the file (mtime/size change), the caller cached a revision from before an external edit, or a directory's contents changed (any entry added/removed/renamed changes the directory revision).

Common situations: Two editor tabs editing the same template; a WebDAV/cloud sync updates the file between read and save; a long-lived client holds a revision across an external re-save; the user edited the file on disk while the app had it open.

Related errors


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