siyuan-note/siyuan · error

Conf.Language(371)

Error message

Conf.Language(371)

What it means

RollbackNotebookHistory restores a deleted notebook from history to data/<boxID>. Before copying, it checks whether the target directory already exists (filelock.IsExist) and aborts with the localized message Conf.Language(371) to avoid overwriting or merging with a live notebook that now occupies the same ID.

Source

Thrown at kernel/model/history.go:644

func RollbackNotebookHistory(historyPath string) (err error) {
	historyPath, err = validateHistoryPath(historyPath)
	if err != nil {
		return
	}
	boxID, err := validateNotebookHistoryPath(historyPath)
	if err != nil {
		return
	}
	if _, loaded := boxLock.LoadOrStore(boxID, true); loaded {
		return errors.New(Conf.Language(239))
	}
	defer boxLock.Delete(boxID)

	from := historyPath
	to := filepath.Join(util.DataDir, boxID)
	if filelock.IsExist(to) {
		return errors.New(Conf.Language(371))
	}

	if err = filelock.CopyNewtimes(from, to); err != nil {
		logging.LogErrorf("copy file [%s] to [%s] failed: %s", from, to, err)
		return
	}

	IncSync()
	ReloadFiletree()
	util.PushMsg(Conf.Language(372), 3000)
	return nil
}

func validateNotebookHistoryPath(historyPath string) (boxID string, err error) {
	rel, err := filepath.Rel(util.HistoryDir, historyPath)
	if err != nil {
		return "", fmt.Errorf("invalid notebook history path [%s]", historyPath)
	}

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Open the existing notebook to confirm whether the restore is still needed; if it is already restored, skip the rollback
  2. Move/rename the existing data/<boxID> directory out of the workspace if you intentionally want to replace it with the historical version, then roll back
  3. Roll back to a different notebook by first clearing the conflicting data — do not attempt to merge
  4. Refresh the history list; the deletion entry is likely stale

Example fix

// before: blind retry keeps failing
rollbackNotebookHistory(historyPath);
// after: check target first and clear intentionally
if (exists(workspaceDir + "/data/<boxID>")) {
  rename(workspaceDir + "/data/<boxID>", backupDir + "/<boxID>.bak");
}
rollbackNotebookHistory(historyPath);
Defensive patterns

Strategy: validation

Validate before calling

async function notebookAlreadyExists(boxID) {
  const r = await fetchPost("/api/filetree/checkFileExist", {path: "/data/" + boxID});
  return r.code === 0 && r.data.isExist === true;
}

Type guard

null

Try / catch

try { await rollbackNotebookHistory(p); } catch (e) { if (isNotebookExistsMessage(e)) { /* prompt user: notebook already exists, skip or replace manually */ } else { throw e; } }

Prevention

When it happens

Trigger: Calling rollbackNotebookHistory for a notebook whose ID directory already exists in the workspace — e.g. the notebook was recreated after deletion (new data under the same ID), the deletion history is old, or a sync brought the notebook back before the rollback ran.

Common situations: User deleted a notebook, then re-imported/re-created it, then tried to restore the old snapshot; duplicate ID collisions after copying notebooks between workspaces; sync resolving a deletion independently on another device; stale history panel entry for an already-restored notebook.

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


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