{"record":{"id":"5512a1cda47b40e1","repo":"router-for-me/CLIProxyAPI","slug":"must-be-a-positive-integer","errorCode":null,"errorMessage":"must be a positive integer","messagePattern":"must be a positive integer","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"error","filePath":"internal/api/handlers/management/logs.go","lineNumber":1224,"sourceCode":"\tvalue := strings.TrimSpace(raw)\n\tif value == \"\" {\n\t\treturn 0\n\t}\n\tts, err := strconv.ParseInt(value, 10, 64)\n\tif err != nil || ts <= 0 {\n\t\treturn 0\n\t}\n\treturn ts\n}\n\nfunc parseLimit(raw string) (int, error) {\n\tvalue := strings.TrimSpace(raw)\n\tif value == \"\" {\n\t\treturn 0, nil\n\t}\n\tlimit, err := strconv.Atoi(value)\n\tif err != nil {\n\t\treturn 0, fmt.Errorf(\"must be a positive integer\")\n\t}\n\tif limit <= 0 {\n\t\treturn 0, fmt.Errorf(\"must be greater than zero\")\n\t}\n\treturn limit, nil\n}\n\nfunc parseTimestamp(line string) int64 {\n\tif strings.HasPrefix(line, \"[\") {\n\t\tline = line[1:]\n\t}\n\tif len(line) < 19 {\n\t\treturn 0\n\t}\n\tcandidate := line[:19]\n\tt, err := time.ParseInLocation(\"2006-01-02 15:04:05\", candidate, time.Local)\n\tif err != nil {\n\t\treturn 0","sourceCodeStart":1206,"sourceCodeEnd":1242,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/api/handlers/management/logs.go#L1206-L1242","documentation":"Returned by parseLimit (logs.go:1224) when the `limit` query parameter of a management log endpoint is non-empty but not parseable by strconv.Atoi. The parameter must be a base-10 integer; floats, suffixed numbers, or arbitrary strings are rejected before any log reading happens.","triggerScenarios":"GET /management/logs?limit=10.5, ?limit=all, ?limit=1e3, or a limit containing whitespace/signs that Atoi rejects. Any log-reading endpoint that accepts `limit` funnels through parseLimit (logs.go:78).","commonSituations":"Dashboards sending UI defaults like 'all' or 'None' as limit; copy-pasted URLs with locale-formatted numbers (1,000); query builders that serialize floats as strings.","solutions":["Send a plain integer: /management/logs?limit=100","Omit the parameter entirely if you want the server default (empty string is accepted as no limit)","Fix the client to validate limit is an integer before building the URL"],"exampleFix":"# before\ncurl -H \"Authorization: Bearer $KEY\" 'http://127.0.0.1:8000/management/logs?limit=10.5'\n\n# after\ncurl -H \"Authorization: Bearer $KEY\" 'http://127.0.0.1:8000/management/logs?limit=10'","handlingStrategy":"validation","validationCode":"// Client-side guard before building the URL.\nif limitParam != \"\" {\n\tif _, err := strconv.Atoi(strings.TrimSpace(limitParam)); err != nil {\n\t\tlimitParam = \"\" // drop invalid value, use server default\n\t}\n}","typeGuard":"function isValidLimit(raw string) bool {\n\tif strings.TrimSpace(raw) === \"\") return true;\n\treturn /^-?\\d+$/.test(raw.trim()) && parseInt(raw.trim(), 10) > 0;\n}","tryCatchPattern":null,"preventionTips":["Always send limit as a plain base-10 integer","Map UI concepts like 'all' to omitting the parameter, not to a keyword","Beware locale formatting (commas) when interpolating numbers into URLs"],"tags":["logs","query-params","validation","management-api"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}