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

  1. Pass prev as the string field ID of an existing key in the view.
  2. Omit prev entirely to place the new key at the end of the current view.
  3. Fetch the view's field IDs first (e.g. via database tools that list keys) and use one of them verbatim.
  4. 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

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


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