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

  1. Pass prev as a string field ID (e.g. "20240101120000-abcdefg")
  2. 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
  3. Coerce the value with String(prev) only if it genuinely is an ID
  4. 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

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


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)