{"record":{"id":"eef32d2c9839b36d","repo":"unslothai/unsloth","slug":"offset-must-be-non-negative","errorCode":null,"errorMessage":"offset must be non-negative","messagePattern":"offset must be non-negative","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"studio/backend/storage/studio_db.py","lineNumber":3296,"sourceCode":"                \"type\": kind,\n                \"name\": part_name\n                if isinstance(part_name, str) and part_name\n                else (\"Chat image\" if kind == \"image\" else \"Chat audio\"),\n                \"contentType\": content_type,\n                \"content\": [part],\n            }\n        )\n    return out\n\n\ndef list_chat_attachments_page(\n    limit: int = 50, offset: int = 0\n) -> tuple[list[dict], Optional[int]]:\n    \"\"\"One bounded page from the normalized attachment inventory.\"\"\"\n    if not 1 <= limit <= 100:\n        raise ValueError(\"limit must be between 1 and 100\")\n    if offset < 0:\n        raise ValueError(\"offset must be non-negative\")\n\n    conn = get_connection()\n    try:\n        _ensure_chat_attachment_inventory_current(conn)\n        rows = conn.execute(\n            \"\"\"\n            SELECT i.attachment_id, i.name, i.type, i.content_type,\n                   i.size_bytes, m.id AS message_id, m.thread_id,\n                   m.created_at, t.title AS thread_title, t.pair_id\n            FROM chat_attachment_inventory i\n            JOIN chat_messages m ON m.id = i.message_id\n            LEFT JOIN chat_threads t ON t.id = m.thread_id\n            ORDER BY m.created_at DESC, m.id ASC, i.attachment_id ASC\n            LIMIT ? OFFSET ?\n            \"\"\",\n            (limit + 1, offset),\n        ).fetchall()\n    finally:","sourceCodeStart":3278,"sourceCodeEnd":3314,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/studio/backend/storage/studio_db.py#L3278-L3314","documentation":"Raised by list_chat_attachments_page() in the studio chat attachment inventory API when the pagination offset is negative. The function is a bounded paging wrapper (limit 1-100, offset >= 0) over the normalized chat_attachment_inventory table, and it validates its arguments before touching SQLite. A negative offset can only come from the caller, never from the database.","triggerScenarios":"Calling list_chat_attachments_page(limit=50, offset=-1) or any negative offset, typically the result of page arithmetic like (page - 1) * limit when the caller passed page=0 or an unvalidated page number from an HTTP query string.","commonSituations":"HTTP handlers mapping ?page=N to offset = (page-1)*limit; CLI tools accepting a --skip argument; off-by-one bugs when page numbering starts at 0 but the formula assumes 1-based pages.","solutions":["Clamp the offset before calling: offset = max(0, offset)","If the offset is derived from a page number, validate page >= 1 before computing (page - 1) * limit","Return an HTTP 422/400 to the client instead of letting the ValueError escape when the offset comes from user input"],"exampleFix":"// before\noffset = (page - 1) * limit\nrows, total = list_chat_attachments_page(limit=limit, offset=offset)\n\n// after\nif page < 1:\n    raise HTTPException(422, \"page must be >= 1\")\noffset = (page - 1) * limit\nrows, total = list_chat_attachments_page(limit=limit, offset=offset)","handlingStrategy":"validation","validationCode":"def safe_page_params(page: int, limit: int) -> tuple[int, int]:\n    if page < 1:\n        raise ValueError(\"page must be >= 1\")\n    if not 1 <= limit <= 100:\n        raise ValueError(\"limit must be between 1 and 100\")\n    offset = (page - 1) * limit\n    return limit, max(0, offset)","typeGuard":"def is_valid_offset(offset: int) -> bool:\n    return isinstance(offset, int) and offset >= 0","tryCatchPattern":"try:\n    rows, total = list_chat_attachments_page(limit=limit, offset=offset)\nexcept ValueError as e:\n    # map to a 4xx for user-supplied paging params\n    raise HTTPException(status_code=422, detail=str(e))","preventionTips":["Never compute offset from an unvalidated page number; assert page >= 1 first","Clamp caller-supplied offsets with max(0, offset) at the API boundary","Keep pagination math in one helper so the >= 1 / >= 0 invariants live in one place"],"tags":["pagination","validation","sqlite","studio-backend"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}