unslothai/unsloth · warning · HTTPException
Invalid version.
Error message
Invalid version.
What it means
HTTP 422 from GET /api/studio/release-notes when the 'version' query parameter fails is_supported_version_query(). That helper requires the string to match a safe version pattern and parse as a real version — 'latest', 'main', path-like strings, or garbage are rejected. The version is only echoed back (not used to look up a release), so this is purely shape validation to let the UI drop stale responses.
Source
Thrown at studio/backend/main.py:1746
"""Return source-aware install metadata without remote update checks."""
return get_studio_install_source_status(UNSLOTH_VERSION)
@app.get("/api/studio/update-status")
def studio_update_status(_current_subject: str = Depends(get_current_subject)):
"""Return source-aware manual update status for browser-served Unsloth."""
return get_studio_update_status(UNSLOTH_VERSION)
@app.get("/api/studio/release-notes")
def studio_release_notes(
version: str = Query(..., max_length = 64),
refresh: bool = Query(False),
_current_subject: str = Depends(get_current_subject),
):
"""Return the newest release's notes. `version` is echoed, not looked up."""
if not is_supported_version_query(version):
raise HTTPException(status_code = 422, detail = "Invalid version.")
return get_release_notes(version, refresh = refresh)
@app.get(
"/api/studio/download-transport-capabilities",
response_model = TransportCapabilities,
)
def studio_download_transport_capabilities(
probe: bool = False, _current_subject: str = Depends(get_current_subject)
):
# Sync def, so FastAPI runs this in the threadpool and an opted-in probe cannot block the loop.
return asdict(get_download_transport_capabilities(probe = probe))
@app.post("/api/shutdown")
async def shutdown_server(request: Request, current_subject: str = Depends(get_current_subject)):
"""Gracefully shut down the Unsloth Studio server.
View on GitHub (pinned to 203007d190)
Solutions
- Pass a concrete version string shaped like a version (e.g. version=2025.8.1 or version=v1.2.3) that the update popup would actually offer.
- Do not send 'latest'/'main' — fetch the update status endpoint first and use the version it reports.
- If you control the client, validate the version before sending (regex for digits/dots/optional v-prefix).
Example fix
# before GET /api/studio/release-notes?version=latest # 422 # after GET /api/studio/release-notes?version=2025.8.1 # 200
Defensive patterns
Strategy: validation
Validate before calling
import re
_VERSION_RE = re.compile(r"^v?\d+(\.\d+)*(-[A-Za-z0-9.]+)?$")
def is_supported_version_query(version: str) -> bool:
c = version.strip()
return len(c) <= 64 and bool(_VERSION_RE.match(c)) and c.lower() not in {"latest", "main"} Type guard
def is_version_query(v: str) -> bool:
"""Narrow a string to a version the release-notes endpoint accepts."""
return isinstance(v, str) and is_supported_version_query(v) Prevention
- Always derive the version from the update-status response instead of hardcoding 'latest'.
- Validate version strings client-side with a strict regex before querying.
- Remember the value is echoed, not looked up — only send versions the UI actually offers.
When it happens
Trigger: Calling /api/studio/release-notes?version=latest, version=main, version=v (unparseable), a path like '../../etc', or any string with characters outside _SAFE_VERSION_PATTERN or longer than the 64-char Query cap.
Common situations: A frontend change that passes a branch name or 'latest' instead of a semver string; manual curl testing with placeholder values; a client built against an older API that accepted arbitrary strings.
Related errors
- Provide either content_base64 or file_ids, not both
- Provide either content_base64 or file_ids
- file_ids must not be empty
- block_id is required when using file_ids
- file_names must be provided and same length as file_ids
AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15).
Data as JSON: /api/errors/12d84fce5990b78e.
Report an issue: GitHub.