siyuan-note/siyuan · error

encrypted database history has no notebook context

Error message

encrypted database history has no notebook context

What it means

When reading attribute view history, ciphertext data was found but the history record carries no notebook (boxID) context. Encrypted database history cannot be decrypted without the notebook-scoped key derivation, so the engine refuses to process ciphertext whose origin notebook is unknown.

Solutions

  1. Place the history file under the correct notebook's history directory and pass the matching boxID
  2. Regenerate the history snapshot from within the encrypted notebook so box context is recorded
  3. If the origin notebook is gone, restore it (with its keys) before restoring the database history
  4. Do not attempt to force-decrypt; recover keys/box metadata from backup instead
Defensive patterns

Strategy: validation

Validate before calling

if (isCiphertext(data) && !boxID) {
  throw new Error('ciphertext history requires notebook context (boxID)');
}

Type guard

function hasBoxContext(data, boxID) {
  return !isCiphertext(data) || !!boxID;
}

Prevention

When it happens

Trigger: restoreAttributeView/backup paths calling readBoundAttributeViewHistory where boxID == '' (history file not associated with a notebook, e.g. collected at top-level history scope) yet util.IsCiphertext(data) is true — typically history files moved between per-notebook and global history directories.

Common situations: Manually relocating history files; third-party backup tools flattening the history directory; version changes that changed where database history is stored relative to the notebook.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/8618cddeef4d1c28. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/attribute_view_block_history.go:36

func boundAttributeViewHistoryPath(historyDir, boxID, avID string) string {
	return filepath.Join(historyDir, boxID, "storage", "av", avID+".json")
}

func readBoundAttributeViewHistory(filename, boxID, avID string) (*av.AttributeView, error) {
	data, err := filelock.ReadFile(filename)
	if err != nil {
		return nil, err
	}
	if boxID != "" {
		if !util.IsCiphertext(data) {
			return nil, errors.New("encrypted database history is plaintext")
		}
		data, err = av.DecryptAVData(boxID, avID, data)
		if err != nil {
			return nil, err
		}
	} else if util.IsCiphertext(data) {
		return nil, errors.New("encrypted database history has no notebook context")
	}
	view, err := av.ParseAttributeViewData(avID, data)
	if err != nil {
		return nil, err
	}
	if view.ID != avID {
		return nil, errors.New("database history ID does not match its filename")
	}
	for _, kv := range view.KeyValues {
		if kv == nil || kv.Key == nil {
			return nil, errors.New("database history contains an invalid field")
		}
		for _, value := range kv.Values {
			if value == nil {
				return nil, errors.New("database history contains an invalid value")
			}
		}
	}

View on GitHub (pinned to 9f775e8a12)