{"record":{"id":"8a0e8a1a8c25bf60","repo":"zylon-ai/private-gpt","slug":"invalid-page-token","errorCode":null,"errorMessage":"Invalid page token","messagePattern":"Invalid page token","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"warning","filePath":"private_gpt/components/skills/repositories/skill_repository.py","lineNumber":447,"sourceCode":"\ndef new_skill_id() -> str:\n    return f\"skill_{uuid.uuid4().hex}\"\n\n\ndef new_skill_version_id() -> str:\n    return f\"skillver_{uuid.uuid4().hex}\"\n\n\ndef new_version_token() -> str:\n    return str(time.time_ns() // 1_000)\n\n\ndef _parse_page(page: str | None) -> int:\n    if page is None:\n        return 0\n    value = int(page)\n    if value < 0:\n        raise ValueError(\"Invalid page token\")\n    return value\n\n\ndef _skill_from_orm(\n    row: SkillORM,\n    latest_version: str | None,\n) -> SkillEntity:\n    source = cast(Literal[\"custom\", \"anthropic\", \"zylon\"], row.source)\n    loading = cast(Literal[\"eager\", \"lazy\"], row.loading)\n    return SkillEntity(\n        id=row.id,\n        collection=row.collection,\n        display_title=row.display_title,\n        source=source,\n        loading=loading,\n        readonly=row.readonly,\n        latest_version=latest_version,\n        created_at=row.created_at,","sourceCodeStart":429,"sourceCodeEnd":465,"githubUrl":"https://github.com/zylon-ai/private-gpt/blob/4a030776a31a901ad80b1bf4d7faa2c1a367efbb/private_gpt/components/skills/repositories/skill_repository.py#L429-L465","documentation":"Pagination in the skill repository uses a plain integer page number as the token (see new_version_token returning epoch milliseconds); _parse_page converts the token with int() and rejects negative values with ValueError('Invalid page token'). Note that int(page) itself will raise ValueError on non-numeric strings before the negativity check — same message space, different trigger — so any malformed token surfaces as this error.","triggerScenarios":"Passing a page token that is not a non-negative integer string — e.g. 'abc', '-1', '1.5', an opaque cursor from another pagination scheme, or None handling that forwards an empty string.","commonSituations":"Clients treating the token as an opaque cursor and mangling it (base64, JSON-encoding); forwarding tokens from a different API; corrupt query parameters; defaulting a missing param to '' instead of None.","solutions":["Echo the token received from the previous response verbatim; do not encode/decode or transform it.","For the first page, omit the parameter entirely (page=None maps to 0) rather than sending an empty string.","Sanitize input: `_t = token if token and token.isdigit() else None` before calling the API.","If you generate tokens yourself, use new_version_token()/new_skill_token() so the format matches."],"exampleFix":"# before\nitems = await repo.list_skills(collection, page=request.query_params.get(\"page\", \"\"))\n# ValueError: Invalid page token\n\n# after\nraw = request.query_params.get(\"page\")\ntoken = raw if raw and raw.isdigit() else None\nitems = await repo.list_skills(collection, page=token)","handlingStrategy":"validation","validationCode":"def safe_page_token(raw: str | None) -> str | None:\n    if raw is None or raw == \"\":\n        return None\n    if not raw.isdigit():\n        raise HTTPException(400, \"page must be a non-negative integer\")\n    return raw","typeGuard":"def is_valid_page_token(value: str | None) -> bool:\n    return value is None or (value.isdigit() and int(value) >= 0)","tryCatchPattern":"try:\n    page = repo._parse_page(raw)  # or the public list call\nexcept ValueError:\n    raw = None  # reset to first page\npage = repo._parse_page(raw)","preventionTips":["Pass tokens through verbatim; never re-encode or trim them.","Use None (omitted param) for the first page instead of an empty string.","Validate query params with isdigit() at the API boundary."],"tags":["skills","pagination","validation","api","tokens"],"backgroundTag":null,"analyzedSha":"4a030776a31a901ad80b1bf4d7faa2c1a367efbb","analyzedAt":"2026-08-15T03:51:26.951Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}