siyuan-note/siyuan · error

invalid notebook ID

Error message

invalid notebook ID

What it means

hasEncryptedNotebookDeleteHistory checks whether any delete-operation history snapshot exists for a given notebook/box ID. It validates the boxID with ast.IsNodeIDPattern and returns the sentinel error 'invalid notebook ID' when the ID is not a well-formed 22-character SiYuan node ID. This guards the filesystem scan from being driven by malformed identifiers.

Source

Thrown at kernel/model/crypto.go:823

		for _, boxEntry := range boxEntries {
			if !boxEntry.IsDir() || !ast.IsNodeIDPattern(boxEntry.Name()) {
				continue
			}
			encrypted, checkErr := isEncryptedHistoryBoxDir(filepath.Join(snapshotDir, boxEntry.Name()))
			if checkErr != nil {
				return nil, checkErr
			}
			if encrypted {
				ret = append(ret, filepath.Join(snapshotDir, boxEntry.Name()))
			}
		}
	}
	return ret, nil
}

func hasEncryptedNotebookDeleteHistory(boxID string) (bool, error) {
	if !ast.IsNodeIDPattern(boxID) {
		return false, errors.New("invalid notebook ID")
	}
	entries, err := os.ReadDir(util.HistoryDir)
	if err != nil {
		if os.IsNotExist(err) {
			return false, nil
		}
		return false, fmt.Errorf("read history dir failed: %w", err)
	}

	deleteSuffix := "-" + HistoryOpDelete
	for _, entry := range entries {
		if !entry.IsDir() || !strings.HasSuffix(entry.Name(), deleteSuffix) {
			continue
		}
		boxDir := filepath.Join(util.HistoryDir, entry.Name(), boxID)
		if !filelock.IsExist(boxDir) {
			continue
		}

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Print/log the offending boxID and compare with a valid node ID (22 chars matching IsNodeIDPattern)
  2. Fix the source of the ID: read it from the notebook's actual .siyuan/conf.json or the notebook list API instead of a hand-maintained config
  3. Guard callers with an IsNodeIDPattern check before invoking, and fail early with a clear message
  4. If the ID came from legacy data, re-derive it via treenoteook/notebook listing rather than trusting stored values

Example fix

// before
hasEncryptedNotebookDeleteHistory(notebook.Name)
// after
if !ast.IsNodeIDPattern(notebook.ID) { return fmt.Errorf("bad boxID %q", notebook.ID) }
hasEncryptedNotebookDeleteHistory(notebook.ID)
Defensive patterns

Strategy: validation

Validate before calling

func validBoxID(id string) bool { return ast.IsNodeIDPattern(id) }
if !validBoxID(boxID) { return fmt.Errorf("refusing: %q is not a node ID", boxID) }

Type guard

func isNodeID(s string) bool { return len(s) == 22 && ast.IsNodeIDPattern(s) }

Try / catch

ok, err := hasEncryptedNotebookDeleteHistory(boxID)
if err != nil && strings.Contains(err.Error(), "invalid notebook ID") {
    return fmt.Errorf("caller passed bad boxID %q", boxID)
}

Prevention

When it happens

Trigger: Calling hasEncryptedNotebookDeleteHistory (directly or via recoverMasterPasswordMigration) with an empty string, a truncated ID, a path fragment, or any string that is not a valid node-ID pattern — e.g. after a config read returns a placeholder boxID, or a test/caller passes a notebook name instead of its ID.

Common situations: Passing a notebook name/slug from configuration instead of the boxID; trimming or truncating IDs when persisting them; older or hand-edited config storing the wrong identifier; tests probing invalid-input behavior.

Understand the failure class

Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.

Related errors


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