siyuan-note/siyuan · error

can not remove [ ] caused by it is a reserved file

Error message

can not remove [%s] caused by it is a reserved file

What it means

RemoveBox rejects notebook IDs whose directory name is a reserved filename (util.IsReservedFilename, e.g. 'assets', 'conf', 'export', 'history', 'snapshots', 'temp' style reserved names under the data directory). Deleting such a directory would corrupt the workspace, so it returns "can not remove [%s] caused by it is a reserved file". It is a deliberate protective refusal, not a bug.

Solutions

  1. Do not attempt to remove reserved directories through the notebook API; delete only real notebooks (valid node-ID directories)
  2. Filter the target list against reserved names before calling removeNotebook
  3. If a reserved-named folder was created by mistake, stop the kernel and remove it manually at the filesystem level only if you are certain it is not workspace-critical

Example fix

// before: removing every data subdir as a notebook
for _, dir := range dataDirs {
    removeNotebook(dir.Name())
}
// after: skip reserved names
for _, dir := range dataDirs {
    if util.IsReservedFilename(dir.Name()) {
        continue
    }
    removeNotebook(dir.Name())
}
Defensive patterns

Strategy: validation

Validate before calling

// mirrors util.IsReservedFilename; apply before calling removeNotebook
var reservedNames = map[string]bool{"assets": true, "conf": true, "export": true, "history": true, "snapshots": true, "temp": true}
function isRemovableNotebook(id string) bool {
    return isValidNotebookID(id) && !reservedNames[id]
}

Try / catch

if err := removeNotebook(boxID); err != nil {
    if strings.Contains(err.Error(), "reserved file") {
        // permanent refusal — do not retry; fix the target list instead
        return fmt.Errorf("skip protected directory %s", boxID)
    }
    return err
}

Prevention

When it happens

Trigger: Calling /api/notebook/removeNotebook with boxID equal to a reserved workspace directory name that happens to pass the node-ID check, or a crafted/legacy notebook directory named with a reserved word.

Common situations: Trying to 'remove' pseudo-notebooks created by tooling under reserved names; scripts iterating the data directory and calling removeNotebook on every subdirectory including reserved ones; migrated workspaces containing manually created folders with reserved names.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/0ba9ad4ce1d7b2d2. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/mount.go:278

	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)
	}
	isUserGuide := IsUserGuide(boxID)
	localPath := filepath.Join(util.DataDir, boxID)
	if !filelock.IsExist(localPath) {
		removeHPathRefreshBox(boxID)
		forgetRuntimeNormalBox(boxID)
		removeMasterPasswordMigrationBox(boxID)
		return
	}
	if !gulu.File.IsDir(localPath) {

View on GitHub (pinned to 9f775e8a12)