siyuan-note/siyuan · error · ErrBoxNotFound

notebook not found

Error message

notebook not found

What it means

Sentinel error ErrBoxNotFound declared at tree.go:202 ('notebook not found'). Returned by tree/block/transaction operations when the referenced notebook (box) ID does not correspond to any known notebook in the configuration. Callers compare with errors.Is(err, model.ErrBoxNotFound). Throw sites include box_doc.go, block.go, mount.go (Conf.Box returns nil), transaction.go, and the AV new-item path.

Source

Thrown at kernel/model/tree.go:202

		}
		// 从绝对路径推导 box 内相对路径作为 AAD
		relPath := filepath.ToSlash(strings.TrimPrefix(localPath, filepath.Join(util.DataDir, boxID)+string(os.PathSeparator)))
		if data, err = DecryptFile(boxID, relPath, dek, data); err != nil {
			logging.LogErrorf("decrypt tree [path=%s] failed: %s", localPath, err)
			return
		}
	}

	ret, err = dataparser.ParseJSONWithoutFix(data, luteEngine.ParseOptions)
	if err != nil {
		logging.LogErrorf("parse json to tree [%s] failed: %s", localPath, err)
		return
	}
	return
}

var (
	ErrBoxNotFound   = errors.New("notebook not found")
	ErrBoxClosed     = errors.New("notebook closed")
	ErrBlockNotFound = errors.New("block not found")
	ErrTreeNotFound  = errors.New("tree not found")
	ErrIndexing      = errors.New("indexing")
	ErrBoxUnindexed  = errors.New("notebook unindexed")
	ErrInvalidID     = errors.New("invalid id")
)

func LoadTreeByBlockIDWithReindex(id string) (ret *parse.Tree, err error) {
	return LoadTreeByBlockIDWithReindexInBox(id, "")
}

// LoadTreeByBlockIDWithReindexInBox 与 LoadTreeByBlockIDWithReindex 一致,但按 boxID 路由 blocktree 查询。
func LoadTreeByBlockIDWithReindexInBox(id, boxID string) (ret *parse.Tree, err error) {
	if "" == id {
		logging.LogWarnf("block id is empty")
		return nil, ErrTreeNotFound
	}

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Verify the boxID exists via Conf.GetBoxes()/Conf.Box(id) before issuing the operation.
  2. Refresh the notebook list in the UI/API client to drop stale boxIDs.
  3. If the notebook was deleted intentionally, propagate the 'not found' state to the user rather than retrying.
  4. Use errors.Is(err, model.ErrBoxNotFound) to distinguish from ErrBoxClosed.

Example fix

// before — assumes box exists
box := Conf.Box(boxID)
tree, err := loadTreeByBlockTree(box) // nil deref or ErrBoxNotFound later

// after — guard first
box := Conf.Box(boxID)
if box == nil {
    return ErrBoxNotFound
}
Defensive patterns

Strategy: validation

Validate before calling

func boxExists(boxID string) bool {
    return model.Conf.Box(boxID) != nil
}

Try / catch

tree, err := model.LoadTreeByBlockIDInBox(id, boxID)
if errors.Is(err, model.ErrBoxNotFound) {
    return fmt.Errorf("notebook %s does not exist", boxID)
}

Prevention

When it happens

Trigger: An operation targets a boxID that is not registered in conf.json notebooks; the notebook was deleted but a stale reference remains; an API call (filetree, AV) passes a boxID that never existed or was removed. mount.go returns it when Conf.Box(id) is nil.

Common situations: A plugin or external API client caches an old boxID after the user deletes the notebook; a sync removed/recreated notebooks leaving dangling references; a typo in the boxID; cross-notebook operation where the target notebook was removed mid-operation.

Related errors


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