{"record":{"id":"b944fee78b1a9356","repo":"Tencent/WeKnora","slug":"invalid-cursor-q","errorCode":null,"errorMessage":"invalid cursor %q","messagePattern":"invalid cursor %q","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"internal/application/service/wiki_page.go","lineNumber":488,"sourceCode":"\tlimit int,\n\tcursor string,\n) (*types.WikiIndexResponse, error) {\n\tindexPage, err := s.GetIndex(ctx, kbID)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"load index page: %w\", err)\n\t}\n\n\tif limit <= 0 {\n\t\tlimit = 50\n\t}\n\tif limit > 200 {\n\t\tlimit = 200\n\t}\n\toffset := 0\n\tif cursor != \"\" {\n\t\tv, parseErr := strconv.Atoi(cursor)\n\t\tif parseErr != nil || v < 0 {\n\t\t\treturn nil, fmt.Errorf(\"invalid cursor %q\", cursor)\n\t\t}\n\t\toffset = v\n\t}\n\n\t// Default to every known content type when the caller passes no\n\t// filter. Any unknown request-time type is passed through verbatim so\n\t// future page types (declared in types/wiki_page.go) start showing\n\t// up in the index the moment the LLM starts creating them, without a\n\t// handler change.\n\tselected := pageTypes\n\tif len(selected) == 0 {\n\t\tselected = append([]string{}, wikiIndexContentPageTypes...)\n\t}\n\n\tgroups := make([]types.WikiIndexGroup, 0, len(selected))\n\tfor _, pt := range selected {\n\t\tentries, total, listErr := s.repo.ListByTypeLight(ctx, kbID, pt, limit, offset)\n\t\tif listErr != nil {","sourceCodeStart":470,"sourceCodeEnd":506,"githubUrl":"https://github.com/Tencent/WeKnora/blob/988cbb03305e055d8ebb7d46d9ac6cc0803cd074/internal/application/service/wiki_page.go#L470-L506","documentation":"GetIndexView parses the opaque pagination cursor as a non-negative integer offset. If the cursor is non-numeric or negative, the service rejects it with 'invalid cursor %q' instead of guessing a default. This is a strict input-validation guard for paginated wiki index views.","triggerScenarios":"Calling GetIndexView (or the HTTP endpoint backed by it) with a cursor query parameter that is not a base-10 integer (e.g. cursor=abc, cursor='', base64/glyph cursors from a different API) or a negative number like cursor=-5.","commonSituations":"Clients sending cursors copied from another paginated API (page tokens vs numeric offsets); frontends echoing back mutated or truncated cursor strings; stale bookmarks with hand-edited URLs; API version mismatches where cursor formats changed.","solutions":["Pass only the exact cursor string previously returned by the API, or omit it entirely to start from offset 0.","Strip whitespace and confirm the cursor is a non-negative integer before sending it.","If migrating from a token-based cursor API, map old tokens to numeric offsets client-side before calling.","Return a clear 400 to the end user prompting them to restart pagination from the first page."],"exampleFix":"// before\nGET /api/wiki/index?cursor=abc\n// after\nGET /api/wiki/index\n// or resume with a valid offset\nGET /api/wiki/index?cursor=200","handlingStrategy":"validation","validationCode":"func validCursor(c string) bool {\n    if c == \"\" { return true } // empty means start from beginning\n    v, err := strconv.Atoi(strings.TrimSpace(c))\n    return err == nil && v >= 0\n}\nif !validCursor(cursor) { cursor = \"\" }","typeGuard":null,"tryCatchPattern":"entries, err := svc.GetIndexView(ctx, kbID, cursor)\nif err != nil && strings.Contains(err.Error(), \"invalid cursor\") {\n    entries, err = svc.GetIndexView(ctx, kbID, \"\") // restart pagination\n}","preventionTips":["Always echo back cursors verbatim from prior responses; never construct them manually","Treat empty cursor as 'first page' rather than sending placeholders","Normalize/trim user-supplied cursor input before sending","When migrating APIs, convert old token cursors to numeric offsets client-side"],"tags":["pagination","validation","cursor"],"backgroundTag":"invalid-pagination-cursor","analyzedSha":"988cbb03305e055d8ebb7d46d9ac6cc0803cd074","analyzedAt":"2026-09-02T14:41:08.344Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}