siyuan-note/siyuan · error

box document ID [%s] does not match box ID [%s]

Error message

box document ID [%s] does not match box ID [%s]

What it means

Thrown by readBoxDocID in kernel/model/box_doc.go when boxDoc.json parses and its boxDocID is a well-formed node ID, but it differs from the box ID derived from the notebook folder that contains the file. A notebook's box document is keyed by that notebook's own ID, so a mismatch means the metadata belongs to a different notebook.

Source

Thrown at kernel/model/box_doc.go:80

	if err != nil {
		if errors.Is(err, os.ErrNotExist) {
			err = nil
		}
		return
	}

	meta := &boxDocMeta{}
	if err = gulu.JSON.UnmarshalJSON(data, meta); err != nil {
		return "", fmt.Errorf("unmarshal box document metadata failed: %w", err)
	}
	if boxDocMetaSpec != meta.Spec {
		return "", fmt.Errorf("unsupported box document metadata spec [%d]", meta.Spec)
	}
	if !ast.IsNodeIDPattern(meta.BoxDocID) {
		return "", fmt.Errorf("invalid box document ID [%s]", meta.BoxDocID)
	}
	if boxID != meta.BoxDocID {
		return "", fmt.Errorf("box document ID [%s] does not match box ID [%s]", meta.BoxDocID, boxID)
	}
	return boxID, nil
}

func writeBoxDocID(boxID string) error {
	meta := &boxDocMeta{Spec: boxDocMetaSpec, BoxDocID: boxID}
	data, err := gulu.JSON.MarshalIndentJSON(meta, "", "  ")
	if err != nil {
		return fmt.Errorf("marshal box document metadata failed: %w", err)
	}
	return filelock.WriteFile(boxDocMetaPath(boxID), data)
}

func IsBoxDocEnabled() bool {
	return nil != Conf && nil != Conf.FileTree && nil != Conf.FileTree.BoxDocEnabled && *Conf.FileTree.BoxDocEnabled
}

func hiddenBoxDocRootIDs() (ret []string) {

View on GitHub (pinned to afa823b6b4)

Solutions

  1. Read the two IDs from the error message: the first is the stored boxDocID, the second is the notebook folder name
  2. Either rename the data folder back to the stored boxDocID, or edit boxDoc.json so boxDocID equals the current folder name
  3. If unsure which is authoritative, delete boxDoc.json and let EnsureBoxDoc regenerate it from the existing hidden /<boxID>.sy document
  4. Re-run the failed mount/sync operation

Example fix

// before: data/20240601120000-newbox01/.siyuan/boxDoc.json
{"spec":1,"boxDocID":"20230101130405-oldbox1"}
// after
{"spec":1,"boxDocID":"20240601120000-newbox01"}
Defensive patterns

Strategy: try-catch

Validate before calling

// read the meta and compare before triggering the failing path
data, err := os.ReadFile(filepath.Join(dataDir, boxID, ".siyuan", "boxDoc.json"))
if err == nil {
	var m struct {
		Spec     int    `json:"spec"`
		BoxDocID string `json:"boxDocID"`
	}
	_ = json.Unmarshal(data, &m)
	if m.BoxDocID != "" && m.BoxDocID != boxID {
		// fix or delete the file before calling EnsureBoxDoc
	}
}

Try / catch

if _, err := model.EnsureBoxDoc(boxID); err != nil {
	if strings.Contains(err.Error(), "does not match box ID") {
		// metadata belongs to another notebook: rename folder back or delete boxDoc.json
	}
	return err
}

Prevention

When it happens

Trigger: Renaming a notebook's data folder without regenerating boxDoc.json; copying a notebook directory to a new folder name; restoring another workspace's .siyuan metadata into this one; sync applying a boxDoc.json from a snapshot of a differently-IDed notebook.

Common situations: Manual workspace surgery or migration; backup/restore into a different workspace; mixed-version sync where one peer wrote meta under an old box ID; copying a notebook via the file manager instead of SiYuan's own duplicate-notebook API.

Related errors


AI-assisted analysis of siyuan-note/siyuan@afa823b6b4 (2026-08-18). Data as JSON: /api/errors/7f81d3821e33e53a. Report an issue: GitHub.