{"record":{"id":"83395bbb13745d61","repo":"usememos/memos","slug":"tags-must-be-compared-with-string-literals","errorCode":null,"errorMessage":"tags must be compared with string literals","messagePattern":"tags must be compared with string literals","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"internal/filter/render.go","lineNumber":456,"sourceCode":"\n\treturn r.renderScalarInCondition(field, cond.Values)\n}\n\nfunc (r *renderer) renderTagInList(values []ValueExpr) (renderResult, error) {\n\tfield, ok := r.schema.ResolveAlias(\"tag\")\n\tif !ok {\n\t\treturn renderResult{}, errors.New(\"tag attribute is not configured\")\n\t}\n\n\tconditions := make([]string, 0, len(values))\n\tfor _, v := range values {\n\t\tlit, err := expectLiteral(v)\n\t\tif err != nil {\n\t\t\treturn renderResult{}, err\n\t\t}\n\t\tstr, ok := lit.(string)\n\t\tif !ok {\n\t\t\treturn renderResult{}, errors.New(\"tags must be compared with string literals\")\n\t\t}\n\n\t\tcondition, err := r.renderJSONListContains(field, str)\n\t\tif err != nil {\n\t\t\treturn renderResult{}, err\n\t\t}\n\t\tconditions = append(conditions, condition.sql)\n\t}\n\n\tif len(conditions) == 0 {\n\t\treturn renderResult{sql: \"1 = 0\"}, nil\n\t}\n\tif len(conditions) == 1 {\n\t\treturn renderResult{sql: conditions[0]}, nil\n\t}\n\treturn renderResult{\n\t\tsql: fmt.Sprintf(\"(%s)\", strings.Join(conditions, \" OR \")),\n\t}, nil","sourceCodeStart":438,"sourceCodeEnd":474,"githubUrl":"https://github.com/usememos/memos/blob/14d757ce1fb31c78590f374bc042f8dbedbc20d7/internal/filter/render.go#L438-L474","documentation":"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.","triggerScenarios":"tag in [1, 2] or tag in [true, \"work\"] — mixing numeric/boolean literals into the IN list; also duration('24h') in tag.","commonSituations":"Numeric tag names written unquoted; copy-pasting mixed-type lists from other filter examples.","solutions":["Quote every element: tag in [\"1\", \"2\"]","Keep the IN list homogeneous strings when filtering tags"],"exampleFix":"// before\n`tag in [1, 2]`\n\n// after\n`tag in [\"1\", \"2\"]`","handlingStrategy":"validation","validationCode":"// Check every IN-list element is a string literal before rendering\nfor _, v := range inCond.Values {\n    lit, err := expectLiteral(v)\n    if err != nil {\n        return err\n    }\n    if _, ok := lit.(string); !ok {\n        return errors.New(\"tag IN list must contain only string literals\")\n    }\n}","typeGuard":"func allStringLiterals(values []filter.ValueExpr) bool {\n    for _, v := range values {\n        lit, ok := v.(*filter.LiteralValue)\n        if !ok {\n            return false\n        }\n        if _, ok := lit.Value.(string); !ok {\n            return false\n        }\n    }\n    return true\n}","tryCatchPattern":"if err := r.Render(cond); err != nil {\n    if strings.Contains(err.Error(), \"string literals\") {\n        return userErr(\"quote every value in the tag IN list\")\n    }\n    return err\n}","preventionTips":["Quote all elements of tag IN lists, including numeric-looking tags","Keep tag lists homogeneous strings in filter expressions"],"tags":["cel","filter","renderer","sql","type-mismatch"],"backgroundTag":null,"analyzedSha":"14d757ce1fb31c78590f374bc042f8dbedbc20d7","analyzedAt":"2026-08-15T09:27:36.538Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}