siyuan-note/siyuan · error

marshal box conf [%s] failed: %w

Error message

marshal box conf [%s] failed: %w

What it means

In Box.SaveConf, after prepareBoxConfForSave succeeds, gulu.JSON.MarshalIndentJSON(persisted, '', ' ') is called; on failure it returns fmt.Errorf('marshal box conf [%s] failed: %w', confPath, err). conf.BoxConf is a plain serializable struct, so under normal operation this never fires — it would only fire if the struct gained a non-marshalable field (chan/func/pointer cycle) or encoding encountered a truly malformed value.

Source

Thrown at kernel/model/box.go:291

	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)
	}

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

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Inspect the BoxConf struct definition (kernel/conf) for any field that JSON cannot encode (func, chan, unsafe.Pointer, or a cycle).
  2. Add json:"-" tags or remove the offending field; ensure all fields are plain data.
  3. Write a unit test that round-trips conf.NewBoxConf() through json.Marshal to catch regressions.

Example fix

// before: a func field breaks marshalling
type BoxConf struct {
    Encrypted bool
    onChange  func()
}

// after: exclude non-serializable fields
type BoxConf struct {
    Encrypted bool
    onChange  func() `json:"-"`
}
Defensive patterns

Strategy: validation

Validate before calling

// Catch marshal regressions in tests before they hit production.
func TestBoxConfSerializable(t *testing.T) {
    b, err := json.Marshal(conf.NewBoxConf())
    if err != nil || !json.Valid(b) { t.Fatalf("BoxConf not serializable: %v", err) }
}

Prevention

When it happens

Trigger: Effectively only reachable from a bug in the BoxConf type definition (a developer adding an unserializable field) or a deeply corrupted in-memory BoxConf. Not a runtime/environmental failure.

Common situations: A code change introduced an unexported chan/func field on BoxConf or a circular reference between conf structs; an experimental branch mutated the struct in a non-serializable way.

Related errors


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