siyuan-note/siyuan · error

encrypted attribute view history is missing valid notebook c

Error message

encrypted attribute view history is missing valid notebook context

What it means

RollbackAttributeViewHistory validates that an encrypted AV history snapshot can be attributed to an encrypted notebook. The history file content is ciphertext (util.IsCiphertext), so the code requires the path under history dir to be boxID/avDir/file.json where pathParts[1] is a valid node ID matching an encrypted box. If the history path does not carry a valid encrypted notebook ID at that position, the rollback cannot determine where the AV definition belongs and it aborts. This protects against restoring encrypted AV definitions to the global storage/av directory where they would be inaccessible or wrongly placed.

Source

Thrown at kernel/model/history.go:692

		return
	}
	// 验证目标文件必须是 AV 定义文件
	if !strings.HasSuffix(historyPath, ".json") || !strings.Contains(filepath.ToSlash(historyPath), "/storage/av/") {
		return fmt.Errorf("invalid AV history path [%s]", historyPath)
	}

	from := historyPath
	// 从路径提取 boxID 判断是否加密笔记本的 AV
	relPath := strings.TrimPrefix(filepath.ToSlash(historyPath), filepath.ToSlash(util.HistoryDir))
	relPath = strings.TrimPrefix(relPath, "/")
	pathParts := strings.SplitN(relPath, "/", 3)
	data, readErr := filelock.ReadFile(from)
	if readErr != nil {
		return readErr
	}
	ciphertext := util.IsCiphertext(data)
	if ciphertext && (len(pathParts) < 3 || !ast.IsNodeIDPattern(pathParts[1]) || !IsEncryptedBox(pathParts[1])) {
		return errors.New("encrypted attribute view history is missing valid notebook context")
	}
	to := filepath.Join(util.DataDir, "storage", "av", filepath.Base(historyPath))
	if len(pathParts) >= 2 && IsEncryptedBox(pathParts[1]) {
		if !ciphertext {
			return fmt.Errorf("encrypted notebook attribute view history is plaintext [%s]", pathParts[1])
		}
		// 加密笔记本的 AV 定义回滚到笔记本级目录
		to = filepath.Join(util.DataDir, pathParts[1], "storage", "av", filepath.Base(historyPath))
		if err = os.MkdirAll(filepath.Dir(to), 0755); err != nil {
			return
		}
	}

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

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Ensure the history path follows <historyDir>/<timestamp>-<op>/<boxID>/storage/av/<avID>.json where boxID is a valid node ID of an encrypted notebook
  2. Verify the boxID segment actually belongs to an encrypted notebook (IsEncryptedBox) — regenerate or re-locate the history under the correct notebook ID directory
  3. If the history file was manually moved, restore the original directory structure from backup
  4. If the AV content is not actually encrypted, check why util.IsCiphertext returned true (wrong file passed in)

Example fix

// before
RollbackAttributeViewHistory("/history/20240101120000-update/storage/av/20240101100000-abc.json") // missing boxID segment
// after
RollbackAttributeViewHistory("/history/20240101120000-update/20240101100000-boxid/storage/av/20240101100000-abc.json")
Defensive patterns

Strategy: validation

Validate before calling

const rel = historyPath.replace(/\\/g, "/").split("/history/").pop().replace(/^\//, "");
const parts = rel.split("/");
const isNodeID = (s) => /^[0-9]{14}-[0-9a-z]{7}$/.test(s);
if (parts.length < 3 || !isNodeID(parts[1])) {
  throw new Error("history path lacks notebook context: " + historyPath);
}

Prevention

When it happens

Trigger: Calling RollbackAttributeViewHistory with a historyPath whose content decrypts as ciphertext but whose relative path under the history directory has fewer than 3 segments, or whose second segment (boxID) is not a node-ID pattern or does not correspond to an encrypted notebook, e.g. a hand-moved or legacy AV history file stored without a notebook-level directory.

Common situations: Manually copying AV history files between machines or backup restores that flatten the history directory structure; upgrading from a version where encrypted-notebook AV history was stored in a different layout; pointing the history viewer at a stale/corrupted history tree.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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