siyuan-note/siyuan · error

view state patch contains too many entries

Error message

view state patch contains too many entries

What it means

PatchViewState limits the total number of entries in a single patch (values plus removeKeys) to maxViewStatePatchCount. Patches exceeding that cap are rejected before validation and merge, keeping view-state storage bounded and patches small.

Source

Thrown at kernel/model/view_state.go:87

	defer viewStateStorageLock.Unlock()

	storage, err := getViewStateStorage()
	if err != nil {
		return nil, err
	}
	state := storage.Views[key]
	if nil == state || nil == state.Data {
		return map[string]any{}, nil
	}
	return cloneViewStateData(state.Data)
}

func PatchViewState(key string, values map[string]any, removeKeys []string) (ret map[string]any, err error) {
	if err = validateViewStateKey(key); err != nil {
		return
	}
	if maxViewStatePatchCount < len(values)+len(removeKeys) {
		return nil, errors.New("view state patch contains too many entries")
	}
	for valueKey := range values {
		if err = validateViewStateDataKey(valueKey); err != nil {
			return nil, err
		}
	}
	for _, removeKey := range removeKeys {
		if err = validateViewStateDataKey(removeKey); err != nil {
			return nil, err
		}
	}
	patch := &viewStatePatch{Values: values, RemoveKeys: removeKeys}
	patchData, marshalErr := gulu.JSON.MarshalJSON(patch)
	if nil != marshalErr {
		return nil, marshalErr
	}
	if maxViewStatePatchBytes < len(patchData) {
		return nil, errors.New("view state patch is too large")

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Split the patch into multiple sequential PatchViewState calls, each under the entry-count cap
  2. Aggregate many flags into a single JSON value under one data key instead of one key per flag
  3. Trim the patch: remove only stale keys and set only changed values

Example fix

// before
ret, err := model.PatchViewState(key, allValues, allRemoveKeys) // hundreds of entries
// after
const batchSize = 50
for i := 0; i < len(allValues); i += batchSize {
    if _, err = model.PatchViewState(key, subMap(allValues, i, batchSize), nil); err != nil {
        break
    }
}
Defensive patterns

Strategy: try-catch

Validate before calling

func isPatchSizeOK(values map[string]any, removeKeys []string) bool {
    return len(values)+len(removeKeys) <= model.MaxViewStatePatchCount
}

Try / catch

_, err := model.PatchViewState(key, values, removeKeys)
if err != nil && strings.Contains(err.Error(), "too many entries") {
    // split into smaller batches and retry
    for batch := range chunkMap(values, 50) {
        if _, err = model.PatchViewState(key, batch, nil); err != nil {
            return err
        }
    }
    return model.PatchViewState(key, nil, removeKeys)
}
return err

Prevention

When it happens

Trigger: Calling PatchViewState with len(values)+len(removeKeys) greater than maxViewStatePatchCount, e.g. one call that sets or removes hundreds of data keys at once for the same view-state key.

Common situations: Persisting a large bulk state blob (many keys) in one patch instead of a few aggregated values; a plugin syncing many UI flags in one call; batch cleanup that removes a long list of keys in a single removeKeys slice.

Understand the failure class

Background: payload too large / request exceeds maximum size: why libraries cap bytes and how to fix oversize payloads — this error's family across 50 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/58df254ec0ed303d. Report an issue: GitHub.