zylon-ai/private-gpt · warning · ValueError

Invalid page token

Error message

Invalid page token

What it means

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.

Source

Thrown at private_gpt/components/skills/repositories/skill_repository.py:447

def new_skill_id() -> str:
    return f"skill_{uuid.uuid4().hex}"


def new_skill_version_id() -> str:
    return f"skillver_{uuid.uuid4().hex}"


def new_version_token() -> str:
    return str(time.time_ns() // 1_000)


def _parse_page(page: str | None) -> int:
    if page is None:
        return 0
    value = int(page)
    if value < 0:
        raise ValueError("Invalid page token")
    return value


def _skill_from_orm(
    row: SkillORM,
    latest_version: str | None,
) -> SkillEntity:
    source = cast(Literal["custom", "anthropic", "zylon"], row.source)
    loading = cast(Literal["eager", "lazy"], row.loading)
    return SkillEntity(
        id=row.id,
        collection=row.collection,
        display_title=row.display_title,
        source=source,
        loading=loading,
        readonly=row.readonly,
        latest_version=latest_version,
        created_at=row.created_at,

View on GitHub (pinned to 4a030776a3)

Solutions

  1. Echo the token received from the previous response verbatim; do not encode/decode or transform it.
  2. For the first page, omit the parameter entirely (page=None maps to 0) rather than sending an empty string.
  3. Sanitize input: `_t = token if token and token.isdigit() else None` before calling the API.
  4. If you generate tokens yourself, use new_version_token()/new_skill_token() so the format matches.

Example fix

# before
items = await repo.list_skills(collection, page=request.query_params.get("page", ""))
# ValueError: Invalid page token

# after
raw = request.query_params.get("page")
token = raw if raw and raw.isdigit() else None
items = await repo.list_skills(collection, page=token)
Defensive patterns

Strategy: validation

Validate before calling

def safe_page_token(raw: str | None) -> str | None:
    if raw is None or raw == "":
        return None
    if not raw.isdigit():
        raise HTTPException(400, "page must be a non-negative integer")
    return raw

Type guard

def is_valid_page_token(value: str | None) -> bool:
    return value is None or (value.isdigit() and int(value) >= 0)

Try / catch

try:
    page = repo._parse_page(raw)  # or the public list call
except ValueError:
    raw = None  # reset to first page
page = repo._parse_page(raw)

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of zylon-ai/private-gpt@4a030776a3 (2026-08-15). Data as JSON: /api/errors/8a0e8a1a8c25bf60. Report an issue: GitHub.