{"record":{"id":"276619fef11732ea","repo":"cloudreve/cloudreve","slug":"failed-to-unmarshal-page-token-w","errorCode":null,"errorMessage":"failed to unmarshal page token: %w","messagePattern":"failed to unmarshal page token: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"inventory/common.go","lineNumber":60,"sourceCode":"\nconst (\n\tOrderDirectionAsc  = OrderDirection(\"asc\")\n\tOrderDirectionDesc = OrderDirection(\"desc\")\n)\n\nvar (\n\tErrTooManyArguments = fmt.Errorf(\"too many arguments\")\n)\n\nfunc pageTokenFromString(s string, hasher hashid.Encoder, idType int) (*PageToken, error) {\n\tsB64Decoded, err := base64.StdEncoding.DecodeString(s)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to decode base64 for page token: %w\", err)\n\t}\n\n\ttoken := &PageToken{}\n\tif err := json.Unmarshal(sB64Decoded, token); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to unmarshal page token: %w\", err)\n\t}\n\n\tid, err := hasher.Decode(token.IDHash, idType)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to decode id: %w\", err)\n\t}\n\n\tif token.Time == nil {\n\t\ttoken.Time = &time.Time{}\n\t}\n\n\ttoken.ID = id\n\treturn token, nil\n}\n\nfunc (p *PageToken) Encode(hasher hashid.Encoder, encodeFunc hashid.EncodeFunc) (string, error) {\n\tp.IDHash = encodeFunc(hasher, p.ID)\n\tres, err := json.Marshal(p)","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/cloudreve/cloudreve/blob/20c95ad73f3a8bcb72887fea91ff31ab24fa1011/inventory/common.go#L42-L78","documentation":"Second failure mode of pageTokenFromString: the string decodes as base64 but the bytes are not valid JSON for the PageToken struct (fields: time, id, string, int, start_with_file). Because PageToken is a struct (not a map), json.Unmarshal rejects syntactically invalid JSON immediately; semantically odd but well-formed JSON is accepted with unknown fields ignored. The ID field is tagged json:\"-\" so tokens minted by hand-crafting JSON cannot carry a raw numeric ID — only the hashid field 'id' is honored.","triggerScenarios":"Passing base64 of a non-JSON payload (\"aGVsbG8=\" for \"hello\"), a JSON array, or a JSON object from a different application's pagination scheme; tokens generated by a different Cloudreve version whose PageToken JSON schema is incompatible; hand-constructed tokens attempting to bypass cursor pagination.","commonSituations":"Integrations that reuse another service's opaque-cursor format; scripts that base64-encode arbitrary JSON hoping to synthesize a page pointer; version skew between the token issuer and consumer (e.g. token cached long-term across an upgrade that changed token fields).","solutions":["Only use tokens issued by the same deployment in the same API version: take next_token from the previous response and feed it back verbatim.","Pre-validate client-side: base64-decode then json.Valid(bytes) before sending; if invalid, restart from page one.","Do not cache page tokens across upgrades; after a Cloudreve upgrade, drop stored tokens and re-list from the beginning.","If you maintain a fork that changed PageToken fields, version the token (e.g. prefix v1:) so mismatches are detectable."],"exampleFix":"// before: hand-crafted token\nraw, _ := json.Marshal(map[string]any{\"page\": 3})\ntoken := base64.StdEncoding.EncodeToString(raw)\nres, err := client.List(ctx, &inventory.ListDavAccountArgs{PageToken: token})\n\n// after: use the server-issued cursor only\nres, err := client.List(ctx, &inventory.ListDavAccountArgs{PageToken: prevRes.PaginationResults.NextPageToken})","handlingStrategy":"validation","validationCode":"// Full client-side structural pre-check\nfunc validPageToken(s string) bool {\n    raw, err := base64.StdEncoding.DecodeString(s)\n    if err != nil || !json.Valid(raw) {\n        return false\n    }\n    var probe map[string]any\n    return json.Unmarshal(raw, &probe) == nil\n}\n\nif args.PageToken != \"\" && !validPageToken(args.PageToken) {\n    args.PageToken = \"\"\n}","typeGuard":"func isPageTokenShapeError(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"failed to unmarshal page token\")\n}","tryCatchPattern":"if err != nil && isPageTokenShapeError(err) {\n    return apiError(400, \"stale or malformed page token; restart pagination\")\n}","preventionTips":["Only replay tokens from the same deployment and API version.","Drop persisted page tokens after upgrading the server.","Prefer restarting from page one over repairing tokens by hand."],"tags":["go","cloudreve","pagination","json","base64","client-input"],"backgroundTag":null,"analyzedSha":"20c95ad73f3a8bcb72887fea91ff31ab24fa1011","analyzedAt":"2026-08-16T01:42:55.403Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}