siyuan-note/siyuan · error

history version is not a document

Error message

history version is not a document

What it means

Returned by loadHistoryDocVersion (history_diff.go:338) when the validated history path does not end in ".sy" (case-insensitive). SiYuan documents on disk are .sy files; only those are valid document-version sources. Non-.sy entries under the history tree (assets, conf.json, boxDocMeta) are intentionally rejected here.

Source

Thrown at kernel/model/history_diff.go:338

	ret, err = filelock.ReadFile(absPath)
	if err != nil || !encrypted {
		return
	}
	dek, err := GetDEKIfUnlocked(blockTree.BoxID)
	if err != nil {
		return nil, errors.New(Conf.Language(314))
	}
	ret, err = DecryptFile(blockTree.BoxID, relPath, dek, ret)
	return
}

func loadHistoryDocVersion(historyPath string) (ret *loadedDocVersion, err error) {
	absPath, err := validateHistoryPath(historyPath)
	if err != nil {
		return nil, err
	}
	if !strings.HasSuffix(strings.ToLower(absPath), ".sy") {
		return nil, errors.New("history version is not a document")
	}
	relPath, err := filepath.Rel(util.HistoryDir, absPath)
	if err != nil {
		return nil, err
	}
	parts := strings.SplitN(filepath.ToSlash(relPath), "/", 3)
	data, err := filelock.ReadFile(absPath)
	if err != nil {
		return nil, err
	}
	ciphertext := util.IsCiphertext(data)
	if ciphertext {
		if len(parts) < 3 || !ast.IsNodeIDPattern(parts[1]) || !IsEncryptedBox(parts[1]) {
			return nil, errors.New("encrypted document history is missing valid notebook context")
		}
		HoldBoxReadLock(parts[1])
		defer ReleaseBoxReadLock(parts[1])
		dek, dekErr := GetDEKIfUnlocked(parts[1])

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Filter history entries client-side to documents only (the history API already tags entry types).
  2. Before calling loadHistoryDocVersion, verify strings.HasSuffix(strings.ToLower(path), ".sy").
  3. If the user genuinely wants to diff an asset, use the asset-history endpoint, not the document-diff one.

Example fix

// before
ref := &DocVersionRef{Type: "history", Path: pickedPath}

// after
if !strings.HasSuffix(strings.ToLower(pickedPath), ".sy") {
    return errors.New("history version is not a document")
}
ref := &DocVersionRef{Type: "history", Path: pickedPath}
Defensive patterns

Strategy: validation

Validate before calling

if !strings.HasSuffix(strings.ToLower(historyPath), ".sy") {
    return errors.New("history version is not a document")
}
// safe to load history doc version

Type guard

// isHistoryDoc reports whether a history path is a .sy document.
func isHistoryDoc(p string) bool {
    return strings.HasSuffix(strings.ToLower(p), ".sy")
}

Prevention

When it happens

Trigger: POST /api/history/diffDocVersions with a history "path" pointing at a directory, an asset file, or a .json metadata file; a frontend that lets the user pick a non-document history row; a hand-crafted historyPath argument.

Common situations: User selects an asset or notebook-config row in the history panel; a script that walks the history dir and feeds every file to the diff API.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/5e7a5aea6d21836b. Report an issue: GitHub.