siyuan-note/siyuan · error
prev must be a string
Error message
prev must be a string
What it means
databasePreviousKeyID resolves where a new key will be placed. If the caller supplies the optional 'prev' argument, it must be a JSON string; any other JSON type triggers 'prev must be a string'. prev names the field ID after which the new key should be inserted in the view.
Source
Thrown at kernel/mcp/tools/database.go:338
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 databaseViewFieldIDs(view *av.View) (ret []string) {View on GitHub (pinned to 8641553a1f)
Solutions
- Pass prev as the string field ID of an existing key in the view.
- Omit prev entirely to place the new key at the end of the current view.
- Fetch the view's field IDs first (e.g. via database tools that list keys) and use one of them verbatim.
- Fix the client schema so prev is typed as string-or-absent.
Example fix
// before
await mcp.call("databaseKeyAdd", { databaseID, prev: 2 });
// after
await mcp.call("databaseKeyAdd", { databaseID, prev: "20240101120000-abcdefg" }); Defensive patterns
Strategy: type-guard
Validate before calling
function normalizePrev(prev) {
if (prev == null || prev === undefined) return undefined; // omit -> view end
if (typeof prev !== 'string') throw new TypeError('prev must be a string field ID');
return prev;
} Type guard
function isStringOrUndefined(v) { return typeof v === 'string' || v === undefined; } Try / catch
try {
await mcp.call("databaseKeyAdd", { databaseID, prev });
} catch (e) {
if (e.message === "prev must be a string") {
// coerce index->ID or drop prev and re-send
}
} Prevention
- Never pass positional indices as prev — always the string field ID
- Omit prev instead of passing null when you want default (view end) placement
- Type the argument as optional string in the client schema
When it happens
Trigger: Calling databaseKeyAdd with prev set to a number, boolean, object, or array instead of a string field ID.
Common situations: Agents echoing back a field order index (e.g. prev: 2) instead of the field's ID; clients passing null explicitly (which is treated as specified but not a string); confusion between key names and field IDs.
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
- keys must be an array
- each key must be an object
- each key requires name and type
- previous key not found in current view: %s
- property %q: x-mcp-header can only be applied to primitive t
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/a5084a8cf2466e7a.
Report an issue: GitHub.