{"record":{"id":"cc527f7433dbeba8","repo":"usememos/memos","slug":"duration-argument-must-be-a-string","errorCode":null,"errorMessage":"duration() argument must be a string","messagePattern":"duration\\(\\) argument must be a string","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"internal/filter/parser.go","lineNumber":624,"sourceCode":"\tcase int64:\n\t\treturn v, true, nil\n\tdefault:\n\t\treturn 0, false, errors.New(\"timestamp() argument must be an RFC3339 string or epoch int\")\n\t}\n}\n\n// evaluateDuration folds duration(\"<go-duration>\") into a number of seconds.\nfunc evaluateDuration(call *exprv1.Expr_Call) (int64, bool, error) {\n\tif len(call.Args) != 1 {\n\t\treturn 0, false, errors.New(\"duration() expects one argument\")\n\t}\n\tvalue, err := getConstValue(call.Args[0])\n\tif err != nil {\n\t\treturn 0, false, errors.Wrap(err, \"duration() only supports literal arguments\")\n\t}\n\tstr, ok := value.(string)\n\tif !ok {\n\t\treturn 0, false, errors.New(\"duration() argument must be a string\")\n\t}\n\td, err := time.ParseDuration(str)\n\tif err != nil {\n\t\treturn 0, false, errors.Wrap(err, \"invalid duration literal\")\n\t}\n\treturn int64(d.Seconds()), true, nil\n}\n\n// timestampAccessors is the set of supported CEL timestamp accessor methods.\nvar timestampAccessors = map[string]bool{\n\t\"getFullYear\":   true,\n\t\"getMonth\":      true,\n\t\"getDate\":       true,\n\t\"getDayOfMonth\": true,\n\t\"getDayOfWeek\":  true,\n\t\"getDayOfYear\":  true,\n\t\"getHours\":      true,\n\t\"getMinutes\":    true,","sourceCodeStart":606,"sourceCodeEnd":642,"githubUrl":"https://github.com/usememos/memos/blob/14d757ce1fb31c78590f374bc042f8dbedbc20d7/internal/filter/parser.go#L606-L642","documentation":"After checking arity and literalness, evaluateDuration requires the literal to be a string parseable by Go's time.ParseDuration. A non-string literal (number or boolean) reaches the type assertion failure and the filter is rejected.","triggerScenarios":"duration(24), duration(86400), or duration(true) — passing a bare number where a quoted Go duration string is required.","commonSituations":"Assuming duration() takes seconds as an integer; converting from another DSL where durations are numeric.","solutions":["Quote a Go duration string: duration(\"24h\")","Use arithmetic on seconds only after conversion: now - duration(\"24h\") stays one call","For raw second math, compare against now - 86400 directly since now folds to epoch seconds"],"exampleFix":"// before\ncreated_ts > now - duration(86400)\n\n// after\ncreated_ts > now - duration(\"24h\")","handlingStrategy":"type-guard","validationCode":"// TS: only allow quoted duration strings into the filter\nconst build = (d: string) => `now - duration(${JSON.stringify(d)})`;\nif (typeof d !== 'string') throw new TypeError('duration must be a string like \"24h\"');","typeGuard":"const isDurationString = (v: unknown): v is string => typeof v === 'string' && /^\\d+(ns|us|µs|ms|s|m|h)+$/.test(v);","tryCatchPattern":null,"preventionTips":["Always quote duration arguments; the DSL takes strings, not numbers","For raw second arithmetic, subtract integers from now directly","Validate with time.ParseDuration equivalents in client code"],"tags":["cel","duration","type-mismatch","filter"],"backgroundTag":null,"analyzedSha":"14d757ce1fb31c78590f374bc042f8dbedbc20d7","analyzedAt":"2026-08-15T09:27:36.538Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}