siyuan-note/siyuan · error

invalid notebook ID

Error message

invalid notebook ID

What it means

First guard in RemoveBox: if !ast.IsNodeIDPattern(boxID) the function returns a raw errors.New('invalid notebook ID') (not localised). ast.IsNodeIDPattern validates that the ID matches SiYuan's node-ID format (the format generated by ast.NewNodeID). This rejects arbitrary/garbage IDs before any filesystem or database work.

Source

Thrown at kernel/model/mount.go:188

	}
	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 251596fc0d)

Solutions

  1. Always pass the notebook ID returned by CreateBox/ListNotebooks, not its display name.
  2. Validate with ast.IsNodeIDPattern(boxID) before calling RemoveBox and reject early.
  3. If exposing removal via an API, normalise and validate the ID at the trust boundary.

Example fix

// before
err := model.RemoveBox("My Notebook") // 'invalid notebook ID'

// after
if !ast.IsNodeIDPattern(boxID) {
    return fmt.Errorf("invalid notebook ID: %q", boxID)
}
err := model.RemoveBox(boxID)
Defensive patterns

Strategy: validation

Validate before calling

if !ast.IsNodeIDPattern(boxID) {
    return fmt.Errorf("invalid notebook ID: %q", boxID)
}

Type guard

// isNotebookID narrows a string to a valid SiYuan notebook ID.
func isNotebookID(id string) bool { return ast.IsNodeIDPattern(id) }

Try / catch

null

Prevention

When it happens

Trigger: Calling RemoveBox with a boxID that is not a valid SiYuan node ID: empty string, a human name, a path, a UUID, a truncated/altered ID, or any string failing the node-ID regex. The check runs before the boxLock and reserved-filename checks.

Common situations: Programmatic caller passing a UI label instead of an ID; ID read from an untrusted source; copy-paste truncation; integration tests using dummy IDs like 'test' or '1'; confusion between notebook name and notebook ID.

Related errors


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