usememos/memos · error

tags must be compared with string literals

Error message

tags must be compared with string literals

What it means

Each value in tag in [...] must be a string literal. renderTagInList calls expectLiteral on every element and then type-asserts it to string; a non-string literal (int, bool, duration, timestamp) fails this check because JSON tag containment can only match strings.

Source

Thrown at internal/filter/render.go:456

	return r.renderScalarInCondition(field, cond.Values)
}

func (r *renderer) renderTagInList(values []ValueExpr) (renderResult, error) {
	field, ok := r.schema.ResolveAlias("tag")
	if !ok {
		return renderResult{}, errors.New("tag attribute is not configured")
	}

	conditions := make([]string, 0, len(values))
	for _, v := range values {
		lit, err := expectLiteral(v)
		if err != nil {
			return renderResult{}, err
		}
		str, ok := lit.(string)
		if !ok {
			return renderResult{}, errors.New("tags must be compared with string literals")
		}

		condition, err := r.renderJSONListContains(field, str)
		if err != nil {
			return renderResult{}, err
		}
		conditions = append(conditions, condition.sql)
	}

	if len(conditions) == 0 {
		return renderResult{sql: "1 = 0"}, nil
	}
	if len(conditions) == 1 {
		return renderResult{sql: conditions[0]}, nil
	}
	return renderResult{
		sql: fmt.Sprintf("(%s)", strings.Join(conditions, " OR ")),
	}, nil

View on GitHub (pinned to 14d757ce1f)

Solutions

  1. Quote every element: tag in ["1", "2"]
  2. Keep the IN list homogeneous strings when filtering tags

Example fix

// before
`tag in [1, 2]`

// after
`tag in ["1", "2"]`
Defensive patterns

Strategy: validation

Validate before calling

// Check every IN-list element is a string literal before rendering
for _, v := range inCond.Values {
    lit, err := expectLiteral(v)
    if err != nil {
        return err
    }
    if _, ok := lit.(string); !ok {
        return errors.New("tag IN list must contain only string literals")
    }
}

Type guard

func allStringLiterals(values []filter.ValueExpr) bool {
    for _, v := range values {
        lit, ok := v.(*filter.LiteralValue)
        if !ok {
            return false
        }
        if _, ok := lit.Value.(string); !ok {
            return false
        }
    }
    return true
}

Try / catch

if err := r.Render(cond); err != nil {
    if strings.Contains(err.Error(), "string literals") {
        return userErr("quote every value in the tag IN list")
    }
    return err
}

Prevention

When it happens

Trigger: tag in [1, 2] or tag in [true, "work"] — mixing numeric/boolean literals into the IN list; also duration('24h') in tag.

Common situations: Numeric tag names written unquoted; copy-pasting mixed-type lists from other filter examples.

Related errors


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