siyuan-note/siyuan · error

Conf.Language(239)

Error message

Conf.Language(239)

What it means

RollbackNotebookHistory takes a per-notebook lock (boxLock.LoadOrStore) to prevent two concurrent rollbacks of the same notebook. If the box is already locked — another rollback of that notebook is in flight — the call fails with the localized message Conf.Language(239) instead of corrupting data with overlapping restores.

Source

Thrown at kernel/model/history.go:637

	encrypted, err := isEncryptedHistoryBoxDir(filepath.Join(util.HistoryDir, parts[0], boxID))
	if err != nil {
		logging.LogErrorf("inspect encrypted history path [%s] failed: %s", absPath, err)
		return true
	}
	return encrypted
}

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

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Wait for the in-flight rollback to finish; the lock is removed (boxLock.Delete) when the first call completes
  2. Disable the rollback button while a request is pending (single-flight on the client)
  3. Serialize rollback requests per notebook in automation scripts (await previous before next)
  4. Retry the request after a short delay if it was rejected due to the concurrent guard

Example fix

// before: fire-and-forget, allows double submit
rollbackNotebookHistory(path); rollbackNotebookHistory(path);
// after: guard against re-entry
let busy = false;
async function rollbackOnce(p) {
  if (busy) return;
  busy = true;
  try { await rollbackNotebookHistory(p); } finally { busy = false; }
}
Defensive patterns

Strategy: try-catch

Validate before calling

const inFlight = new Set();
function canStartNotebookRollback(boxID) { return !inFlight.has(boxID); }

Type guard

null

Try / catch

try { await rollbackNotebookHistory(p); } catch (e) { if (isI18nBusyMessage(e)) { await sleep(1000); /* retry once */ } else { throw e; } }

Prevention

When it happens

Trigger: Two simultaneous calls to rollbackNotebookHistory (API /api/history/rollbackNotebookHistory) for the same notebook ID, e.g. double-clicking the rollback button or triggering rollback from two clients while the first copy operation is still running.

Common situations: Impatient users clicking rollback repeatedly; automation scripts firing overlapping requests; multi-window/multi-device sessions issuing the same rollback; slow copy of a large notebook holding the lock for a long time.

Related errors


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