usememos/memos · error

startsWith argument must be a string

Error message

startsWith argument must be a string

What it means

The argument to startsWith() inside a comprehension predicate must be a compile-time string constant. If the argument resolves to a non-string constant (int, bool, etc.), this error is thrown; a non-constant argument produces the wrapped 'startsWith argument must be a constant string' instead.

Source

Thrown at internal/filter/parser.go:970

// buildStartsWithPredicate extracts the pattern from t.startsWith("prefix").
func buildStartsWithPredicate(call *exprv1.Expr_Call, iterVar string) (PredicateExpr, error) {
	// Verify the target is the iteration variable
	if target := call.Target.GetIdentExpr(); target == nil || target.GetName() != iterVar {
		return nil, errors.Errorf("startsWith target must be the iteration variable %q", iterVar)
	}

	if len(call.Args) != 1 {
		return nil, errors.New("startsWith expects exactly one argument")
	}

	prefix, err := getConstValue(call.Args[0])
	if err != nil {
		return nil, errors.Wrap(err, "startsWith argument must be a constant string")
	}

	prefixStr, ok := prefix.(string)
	if !ok {
		return nil, errors.New("startsWith argument must be a string")
	}

	return &StartsWithPredicate{Prefix: prefixStr}, nil
}

// buildEndsWithPredicate extracts the pattern from t.endsWith("suffix").
func buildEndsWithPredicate(call *exprv1.Expr_Call, iterVar string) (PredicateExpr, error) {
	if target := call.Target.GetIdentExpr(); target == nil || target.GetName() != iterVar {
		return nil, errors.Errorf("endsWith target must be the iteration variable %q", iterVar)
	}

	if len(call.Args) != 1 {
		return nil, errors.New("endsWith expects exactly one argument")
	}

	suffix, err := getConstValue(call.Args[0])
	if err != nil {
		return nil, errors.Wrap(err, "endsWith argument must be a constant string")

View on GitHub (pinned to 14d757ce1f)

Solutions

  1. Quote the prefix: tag.exists(t, t.startsWith("work"))
  2. Remember the prefix is a SQL LIKE 'prefix%' pattern, so it must be a literal string

Example fix

// before
`tag.exists(t, t.startsWith(5))`

// after
`tag.exists(t, t.startsWith("work"))`
Defensive patterns

Strategy: validation

Validate before calling

var badStartsWith = regexp.MustCompile(`startsWith\s*\(\s*[^"']`)

func validateStartsWith(expr string) error {
    if badStartsWith.MatchString(expr) {
        return errors.New("startsWith needs a quoted string prefix")
    }
    return nil
}

Try / catch

if err := r.Render(cond); err != nil {
    if strings.Contains(err.Error(), "startsWith argument must be a string") {
        return userErr("quote the startsWith prefix, e.g. t.startsWith(\"work\")")
    }
    return err
}

Prevention

When it happens

Trigger: tag.exists(t, t.startsWith(5)) — passing a numeric literal instead of a quoted string; similarly boolean or duration literals.

Common situations: Users write unquoted prefixes or assume startsWith coerces types.

Related errors


AI-assisted analysis of usememos/memos@14d757ce1f (2026-08-15). Data as JSON: /api/errors/275ae6eb1ffe3938. Report an issue: GitHub.