siyuan-note/siyuan · error

invalid notebook ID

Error message

invalid notebook ID

What it means

RemoveBox validates that boxID matches ast.IsNodeIDPattern (a SiYuan node-ID format: 14-digit timestamp + 6 random chars, e.g. 20240102150405-abcdefg) before doing any work. If the ID is not a valid node ID it returns the literal error "invalid notebook ID". This guards the notebook-removal API against malformed or arbitrary path-like identifiers.

Solutions

  1. Fetch the correct notebook ID first via /api/notebook/lsNotebooks and pass the id field exactly (20 chars, format tttttttttttttt-xxxxxxx)
  2. Check the value for stray whitespace, quotes, or path separators; trim or re-read it from the notebooks list response
  3. If the ID comes from stored config, verify the notebook still exists and regenerate the reference from lsNotebooks

Example fix

// before: using notebook name as ID
fetchPost("/api/notebook/removeNotebook", {notebook: "My Notes"})
// after: resolve ID first
const notebooks = await fetchPost("/api/notebook/lsNotebooks", {})
const box = notebooks.notebooks.find(nb => nb.name === "My Notes")
await fetchPost("/api/notebook/removeNotebook", {notebook: box.id})
Defensive patterns

Strategy: validation

Validate before calling

var nodeIDRe = regexp.MustCompile(`^\d{14}-[0-9a-z]{7}$`)
func isValidNotebookID(id string) bool { return nodeIDRe.MatchString(id) }
// call removeNotebook only if isValidNotebookID(notebookID)

Try / catch

err := removeNotebook(boxID)
if err != nil && err.Error() == "invalid notebook ID" {
    // re-resolve the ID via lsNotebooks and retry once
}

Prevention

When it happens

Trigger: Calling the kernel API /api/notebook/removeNotebook (or RemoveBox directly) with an ID that is not a 20-character node ID — e.g. a notebook name, a relative path, an empty string, an old-style ID, or a value copied from a different field.

Common situations: Scripting against the HTTP API with the notebook name instead of its ID; stale client code holding IDs from a pre-rename dataset; plugin code concatenating path components into the ID; trimmed/truncated IDs from logs or config files.

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@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/f476d6e602718b63. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/mount.go:269

	}
	return
}

func collectBoxDeletedAttributeViewBlocks(boxID string) (ret map[string]map[string]struct{}, err error) {
	rootIDs := treenode.GetRootBlockIDsByBoxID(boxID)
	if 1 > len(rootIDs) {
		return map[string]map[string]struct{}{}, nil
	}
	boundAVIDs, err := sql.QueryBoundBlockAVIDsInBox(nil, rootIDs, boxID)
	if nil != err {
		return nil, err
	}
	return groupDeletedAttributeViewBlocks(boundAVIDs), nil
}

func RemoveBox(boxID string) (err error) {
	if !ast.IsNodeIDPattern(boxID) {
		return errors.New("invalid notebook ID")
	}
	if _, loaded := boxLock.LoadOrStore(boxID, true); loaded {
		err = errors.New(Conf.language(239))
		return
	}
	defer boxLock.Delete(boxID)

	if util.IsReservedFilename(boxID) {
		return fmt.Errorf("can not remove [%s] caused by it is a reserved file", boxID)
	}

	FlushTxQueue()
	sql.FlushQueue()
	// 索引和笔记本目录删除后无法再读取 custom-avs,需提前收集;实际删除成功后再清理绑定行。
	deletedAttrViewBlockIDs, err := collectBoxDeletedAttributeViewBlocks(boxID)
	if nil != err {
		return fmt.Errorf("query database-bound blocks in notebook [%s] failed: %w", boxID, err)
	}

View on GitHub (pinned to 9f775e8a12)