siyuan-note/siyuan · error

invalid box ID [ ]

Error message

invalid box ID [%s]

What it means

saveConf0 validates that the box ID is a syntactically valid SiYuan node ID (ast.IsNodeIDPattern, a 14-char lowercase alnum string) before writing conf.json. This error means the Box struct carries an ID that is not a valid notebook identifier, so the write is refused to avoid creating garbage directories under DataDir.

Solutions

  1. Ensure the Box was created via a valid notebook creation path so the ID matches the node-ID pattern (14 lowercase alphanumerics)
  2. Check the directory name under the workspace data/ folder — it must be a valid node ID
  3. If constructing a Box in tests, use a generated valid ID (e.g. via the ID generator) instead of a literal
  4. Guard callers: verify ast.IsNodeIDPattern(box.ID) before calling SaveConf

Example fix

// before
box := &Box{ID: "my-notebook"}
err := box.SaveConf(conf)
// after
if !ast.IsNodeIDPattern(box.ID) {
    return fmt.Errorf("refusing to save conf: invalid box ID %q", box.ID)
}
err := box.SaveConf(conf)
Defensive patterns

Strategy: validation

Validate before calling

// Go: validate the box ID before saving
if !ast.IsNodeIDPattern(box.ID) {
    return fmt.Errorf("invalid notebook ID %q", box.ID)
}

Try / catch

if err := box.SaveConf(conf); err != nil {
    if strings.Contains(err.Error(), "invalid box ID") {
        // recreate Box from a valid notebook path
    }
}

Prevention

When it happens

Trigger: Calling Box.SaveConf on a Box whose ID was constructed programmatically (not loaded from disk), e.g. an empty ID, a path fragment, or an ID with invalid characters. Raised by saveConf0 which is invoked from SaveConf whenever conf.json must be written.

Common situations: Tests constructing Box{ID: "test"} directly; plugins or scripts passing fabricated notebook IDs; corrupted in-memory state after a failed box load; migrating data with legacy/renamed notebook folder names.

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/52ad5ee8ee84137a. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/box.go:375

	if err = box.saveConf0(newData); err != nil {
		return err
	}
	return syncBoxConfCryptoBackup(box.ID, persisted)
}

func syncBoxConfCryptoBackup(boxID string, boxConf *conf.BoxConf) error {
	if !boxConf.Encrypted || boxConf.BoxCrypt == nil {
		return nil
	}
	if needWriteNotebookCryptBackup(boxID, boxConf.BoxCrypt) {
		return writeNotebookCryptBackup(boxID, boxConf.BoxCrypt)
	}
	return nil
}

func (box *Box) saveConf0(data []byte) error {
	if !ast.IsNodeIDPattern(box.ID) {
		return fmt.Errorf("invalid box ID [%s]", box.ID)
	}

	confPath := filepath.Join(util.DataDir, box.ID, ".siyuan/conf.json")
	if err := os.MkdirAll(filepath.Join(util.DataDir, box.ID, ".siyuan"), 0755); err != nil {
		return fmt.Errorf("mkdir box conf dir failed: %w", err)
	}
	if err := filelock.WriteFile(confPath, data); err != nil {
		util.ReportFileSysFatalError(err)
		return fmt.Errorf("write box conf [%s] failed: %w", confPath, err)
	}
	invalidateEncryptedPublishAccessCache()
	return nil
}

// validateBoxPath 校验 box 内相对路径,拒绝 .. 和绝对路径,确保最终路径在 <DataDir>/<boxID>/ 内。
func (box *Box) validateBoxPath(p string) (string, error) {
	return filesys.ValidateBoxRelativePath(box.ID, p)
}

View on GitHub (pinned to 9f775e8a12)