siyuan-note/siyuan · error

prepare box conf [%s] failed: %w

Error message

prepare box conf [%s] failed: %w

What it means

Box.SaveConf calls prepareBoxConfForSave(box.ID, conf); on error it returns fmt.Errorf('prepare box conf [%s] failed: %w', confPath, err) where confPath is <DataDir>/<boxID>/.siyuan/conf.json. prepareBoxConfForSave (box_conf_crypto.go:88) can fail for several distinct reasons, all wrapped here: nil conf ('notebook configuration is missing'), saving an encrypted notebook as normal ('encrypted notebook cannot be saved as a normal notebook'), an encrypted box in error state ('encrypted notebook is in an error state'), missing encrypted metadata, or a decryption failure on the metadata-reuse path. The %w lets the caller unwrap the real cause.

Source

Thrown at kernel/model/box.go:287

		logging.LogErrorf("parse box conf [%s] failed: %s", confPath, err)
		return
	}

	if ret.Encrypted {
		if err = revealBoxMetadataIfUnlocked(box.ID, ret); err != nil {
			logging.LogErrorf("decrypt encrypted notebook metadata [%s] failed: %s", box.ID, err)
		}
	} else {
		ret.Icon = filterBoxIcon(ret.Icon)
	}
	return
}

func (box *Box) SaveConf(conf *conf.BoxConf) error {
	confPath := filepath.Join(util.DataDir, box.ID, ".siyuan/conf.json")
	persisted, err := prepareBoxConfForSave(box.ID, conf)
	if err != nil {
		return fmt.Errorf("prepare box conf [%s] failed: %w", confPath, err)
	}
	newData, err := gulu.JSON.MarshalIndentJSON(persisted, "", "  ")
	if err != nil {
		return fmt.Errorf("marshal box conf [%s] failed: %w", confPath, err)
	}

	oldData, err := filelock.ReadFile(confPath)
	if err != nil {
		if err = box.saveConf0(newData); err != nil {
			return err
		}
		return syncBoxConfCryptoBackup(box.ID, persisted)
	}

	if bytes.Equal(newData, oldData) {
		return syncBoxConfCryptoBackup(box.ID, persisted)
	}

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Unwrap the returned error to read the underlying message — the fix depends on which prepareBoxConfForSave branch failed.
  2. If the box is in EncryptedBoxStateError, re-unlock it (re-enter the passphrase) to clear the state before saving.
  3. If BoxCrypt is nil while Encrypted=true, do not hand-edit conf.json — re-establish encryption through the UI/CLI.
  4. Ensure a cached DEK exists (notebook unlocked) before changing encrypted-notebook settings.

Example fix

// before
if err := box.SaveConf(conf); err != nil {
    logging.LogErrorf("save conf failed: %s", err)
}

// after: surface the wrapped root cause
if err := box.SaveConf(conf); err != nil {
    var root error = err
    if u := errors.Unwrap(err); u != nil { root = u }
    logging.LogErrorf("save conf failed (root=%s): %s", root, err)
}
Defensive patterns

Strategy: try-catch

Validate before calling

// For encrypted notebooks, ensure a cached DEK and non-error state before saving.
if model.IsEncryptedBox(boxID) {
    if model.GetEncryptedBoxState(boxID) == model.EncryptedBoxStateError { unlockFirst(boxID) }
    if _, ok := model.CachedDEKCopy(boxID); !ok { unlockFirst(boxID) }
}

Try / catch

// Always unwrap so the real prepareBoxConfForSave cause is visible.
if err := box.SaveConf(conf); err != nil {
    log.Errorw("save box conf", "box", box.ID, "err", err, "root", errors.Unwrap(err))
    return err
}

Prevention

When it happens

Trigger: Any notebook configuration change for an encrypted notebook — sort mode, icon, etc. — coming through the filetree/attr flows that call Box.SaveConf (e.g. SetBlockAttrs/BatchSetBlockAttrs setting an 'icon' on a box-doc at blockial.go:192/238), or a direct notebook settings save, when the encryption state is inconsistent (BoxCrypt nil'd while Encrypted=true, box stuck in EncryptedBoxStateError, DEK not cached so metadata can't be reused/encrypted).

Common situations: Unlock failed and left the box in error state; a prior migration cleared BoxCrypt; the conf.json was hand-edited to remove key material; switching encryption off without going through the proper disable flow.

Related errors


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