siyuan-note/siyuan · error
local storage value for key [%s] must not be empty
Error message
local storage value for key [%s] must not be empty
What it means
SetLocalStorageVals rejects any entry whose value is nil with the message 'local storage value for key [%s] must not be empty'. Local storage is a free-form key/value map persisted in the workspace, and the kernel treats a nil value as programmer error rather than an implicit delete (deletion goes through RemoveLocalStorageVals). The error includes the offending key for diagnostics.
Source
Thrown at kernel/model/storage.go:61
defer localStorageLock.Unlock()
return getLocalStorage()
}
func SetLocalStorage(val map[string]any) (err error) {
localStorageLock.Lock()
defer localStorageLock.Unlock()
return setLocalStorage(val)
}
func SetLocalStorageVals(keyVals map[string]any) (setKeyVals map[string]any, err error) {
localStorageLock.Lock()
defer localStorageLock.Unlock()
setKeyVals = make(map[string]any, len(keyVals))
localStorage := getLocalStorage()
for k, v := range keyVals {
if v == nil {
err = fmt.Errorf("local storage value for key [%s] must not be empty", k)
return
}
localStorage[k] = v
setKeyVals[k] = v
}
err = setLocalStorage(localStorage)
return
}
func RemoveLocalStorageVals(keys []string) (err error) {
localStorageLock.Lock()
defer localStorageLock.Unlock()
localStorage := getLocalStorage()
for _, key := range keys {
delete(localStorage, key)
}
return setLocalStorage(localStorage)View on GitHub (pinned to 251596fc0d)
Solutions
- Replace nil with an explicit empty value: "" for strings, 0 for numbers, false for bools, or an empty object/array.
- If the intent is to remove the key, call /api/storage/removeLocalStorageVals with that key instead.
- Filter null entries out of the map on the client before sending.
Example fix
// before
{ "keyVals": { "layout": null } } // -> error 902
// after
{ "keyVals": { "layout": "" } }
// or to delete: POST /api/storage/removeLocalStorageVals { "keys": ["layout"] } Defensive patterns
Strategy: validation
Validate before calling
// TypeScript: strip nulls, coerce to explicit empties, or route to remove
function sanitizeKeyVals(kv: Record<string, unknown>) {
const out: Record<string, unknown> = {};
for (const [k, v] of Object.entries(kv)) {
if (v === null || v === undefined) continue; // or call removeLocalStorageVals
out[k] = v;
}
return out;
} Type guard
// TypeScript const isStorableValue = (v: unknown): boolean => v !== null && v !== undefined;
Prevention
- Never serialize undefined/null as a localStorage value; treat absence as a delete request.
- Distinguish 'set to empty' (send "") from 'delete' (call remove API).
- Validate the payload map on the client; reject keys with null before fetch.
When it happens
Trigger: POST /api/storage/setLocalStorageVals with a JSON body whose map contains a key mapped to null. POST /api/storage/setLocalStorageVal (single) wraps the value into the same map path, so a null single value hits the same guard.
Common situations: A frontend/plugin stores a value that was just set to null by application logic (e.g. cleared layout state). A deserialization step produced nil where an empty string or 0 was intended. An MCP or script client serializes a struct with an unset pointer field.
Related errors
- task list item marker length should be 1
- task list item marker can not be [ or ]
- task list item marker not found
- invalid import token
- no file found
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/84a1a0b65752fdbe.
Report an issue: GitHub.