siyuan-note/siyuan · error

encrypted document history is missing notebook context

Error message

encrypted document history is missing notebook context

What it means

GetDocHistoryContent reads a history snapshot file. If the file bytes are ciphertext, the history path must contain a notebook ID segment (history/<timestamp>/<boxID>/<file>) so the kernel can find the right notebook. When the path has fewer than 3 parts or the second segment is not a node-ID-shaped notebook ID, it errors with 'encrypted document history is missing notebook context'.

Source

Thrown at kernel/model/history.go:183

	historyPath, err = validateHistoryPath(historyPath)
	if err != nil {
		return
	}

	data, err := filelock.ReadFile(historyPath)
	if err != nil {
		logging.LogErrorf("read file [%s] failed: %s", historyPath, err)
		return
	}

	// 加密笔记本的历史是密文,按路径里的 boxID 解密后解析
	relPath := strings.TrimPrefix(filepath.ToSlash(historyPath), filepath.ToSlash(util.HistoryDir))
	relPath = strings.TrimPrefix(relPath, "/")
	pathParts := strings.SplitN(relPath, "/", 3)
	ciphertext := util.IsCiphertext(data)
	if ciphertext {
		if len(pathParts) < 3 || !ast.IsNodeIDPattern(pathParts[1]) {
			err = errors.New("encrypted document history is missing notebook context")
			return
		}
		histBoxID := pathParts[1]
		if !IsEncryptedBox(histBoxID) {
			err = fmt.Errorf("encrypted document history has no matching notebook [%s]", histBoxID)
			return
		}
		HoldBoxReadLock(histBoxID)
		defer ReleaseBoxReadLock(histBoxID)
		dek, dekErr := GetDEKIfUnlocked(histBoxID)
		if dekErr != nil {
			err = errors.New(Conf.Language(314))
			return
		}
		data, err = DecryptFile(histBoxID, pathParts[2], dek, data)
		if err != nil {
			logging.LogErrorf("decrypt history [%s] failed: %s", historyPath, err)
			return

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Pass the full original historyPath returned by getDocHistoryContent/history listing endpoints, not a reconstructed path
  2. Verify the path matches history/<ts>/<boxID>/<name> for encrypted notebooks
  3. Restore or re-index the history directory if it was manually modified

Example fix

// before
const p = '/history/20240101120000/index.sy'; // missing boxID segment
// after
const p = '/history/20240101120000/20240101100000-abc1234567890/index.sy';
Defensive patterns

Strategy: validation

Validate before calling

// expected layout: history/<ts>/<boxID>/<name>
const parts = historyPath.replace(/^\/history\//, '').split('/');
if (parts.length < 3 || !/^[0-9a-v]{20}$/.test(parts[1])) throw new Error('history path missing notebook context');

Type guard

const hasBoxContext = (p) => { const parts = p.replace(/^\/history\//, '').split('/'); return parts.length >= 3 && /^[0-9a-v]{20}$/.test(parts[1]); };

Try / catch

try {
  return await fetchPost('/api/history/getDocHistoryContent', {historyPath});
} catch (e) {
  if (String(e.msg).includes('missing notebook context')) {
    // re-fetch the canonical history path from the history listing API
  } else throw e;
}

Prevention

When it happens

Trigger: Requesting a history file whose path lacks the notebook-ID segment, or where pathParts[1] is not a valid node ID, while the file content is detected as ciphertext (util.IsCiphertext).

Common situations: Hand-built history paths in plugins/scripts; very old history layout created before encrypted-notebook support; corrupted or renamed history directories.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


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