siyuan-note/siyuan · error
prev must be a string
Error message
prev must be a string
What it means
databasePreviousKeyID (kernel/mcp/tools/database.go) resolves the key after which a new key is inserted. If the caller explicitly supplies the optional `prev` argument, it must be a string key/field ID; any other JSON type is rejected with this error.
Solutions
- Pass prev as a string field ID (e.g. "20240101120000-abcdefg")
- Omit prev entirely to let the key be appended after the current view's last field (empty string means view start position) in the default flow
- Coerce the value with String(prev) only if it genuinely is an ID
- Check the current view's field IDs via database tooling before choosing prev
Example fix
// before
{prev: 2}
// after
{prev: "20240101120000-abcdefg"} Defensive patterns
Strategy: validation
Validate before calling
if ("prev" in args && typeof args.prev !== "string") throw new Error("prev must be a string field ID"); Type guard
const isFieldId = (v) => typeof v === "string" && /^[0-9]{14}-[a-z0-9]+$/.test(v); Try / catch
try { await call("database_key_add", args) } catch (e) { if (String(e).includes("prev must be a string")) { args.prev = String(args.prev); } else throw e; } Prevention
- Never pass numeric positions as prev
- Pass field-ID strings or omit prev
- Coerce interpolations to string explicitly
When it happens
Trigger: Calling database key add with prev set to a number, boolean, null-ish object, or array — e.g. prev: 3 or prev: ["id"].
Common situations: Clients passing position indexes instead of IDs; LLM-generated args guessing prev is numeric; template code that interpolates a non-string variable.
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
Related errors
- attr must be a string or null (got %T)
- each key must be an object
- each key requires name and type
- keys must be an array
- --av and --ids are required
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/a5084a8cf2466e7a.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/mcp/tools/database.go:346
key := &av.Key{ID: keyID, Name: name, Type: av.KeyType(keyType), Icon: icon, DateFormat: av.DateDisplayFormatFull}
if nil != attrView {
if storedKey, getErr := attrView.GetKey(keyID); nil == getErr {
key = storedKey
}
}
return databaseSuccess("key_add", map[string]any{"id": id, "key": key})
}
func databasePreviousKeyID(attrView *av.AttributeView, args map[string]any) (ret string, err error) {
view, err := attrView.GetFirstView()
if nil != err {
return "", err
}
fieldIDs := databaseViewFieldIDs(view)
if value, specified := args["prev"]; specified {
prev, ok := value.(string)
if !ok {
return "", errors.New("prev must be a string")
}
if "" == prev {
return "", nil
}
for _, fieldID := range fieldIDs {
if fieldID == prev {
return prev, nil
}
}
return "", fmt.Errorf("previous key not found in current view: %s", prev)
}
if 0 < len(fieldIDs) {
return fieldIDs[len(fieldIDs)-1], nil
}
return "", nil
}
func databaseKeySetTemplate(args map[string]any) (CallToolResult, error) {View on GitHub (pinned to 9f775e8a12)