{"record":{"id":"69949d9ae1be6468","repo":"unslothai/unsloth","slug":"refused-notebook-fetch-from-host-r-not-in-allow","errorCode":null,"errorMessage":"Refused notebook fetch from {host!r}: not in allowlist {sorted(_ALLOWED_NOTEBOOK_HOSTS)}","messagePattern":"Refused notebook fetch from (.+?): not in allowlist (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"warning","filePath":"scripts/notebook_to_python.py","lineNumber":65,"sourceCode":"    if parsed.netloc != \"github.com\" or \"/blob/\" not in parsed.path:\n        return url\n    new_path = parsed.path.replace(\"/blob/\", \"/\", 1)\n    return urllib.parse.urlunparse(\n        parsed._replace(netloc = \"raw.githubusercontent.com\", path = new_path)\n    )\n\n\ndef download_notebook(url: str) -> tuple[str, str]:\n    \"\"\"Download notebook from URL. Returns (content, filename).\"\"\"\n    raw_url = github_blob_to_raw(url)\n\n    parsed = urllib.parse.urlparse(raw_url)\n    filename = os.path.basename(urllib.parse.unquote(parsed.path))\n\n    # Host allowlist: refuse to fetch from anything we don't recognise.\n    host = parsed.hostname\n    if host not in _ALLOWED_NOTEBOOK_HOSTS:\n        raise ValueError(\n            f\"Refused notebook fetch from {host!r}: not in allowlist \"\n            f\"{sorted(_ALLOWED_NOTEBOOK_HOSTS)}\"\n        )\n\n    print(f\"Downloading {url}...\")\n    with urllib.request.urlopen(raw_url, timeout = 60) as response:\n        content = response.read().decode(\"utf-8\")\n\n    return content, filename\n\n\ndef is_url(path: str) -> bool:\n    \"\"\"Check if path is a URL.\"\"\"\n    return path.startswith(\"http://\") or path.startswith(\"https://\")\n\n\ndef replace_colab_paths(source: str) -> str:\n    \"\"\"Replace Colab-specific /content/ paths with current working directory.\"\"\"","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/scripts/notebook_to_python.py#L47-L83","documentation":"Thrown by parseBaseUrlForProvider (chat-providers-dialog.tsx:476-491) when the Base URL field is empty (after trim) but the provider being configured requires one. Required is determined per provider type: hosted registry entries have their own base_url, while custom/user-supplied connections (OpenAI-compatible gateways, local servers) must be told where to send requests.","triggerScenarios":"Saving a custom provider connection with an empty Base URL field — required === true for that providerType. parseBaseUrlForProvider is called from the save/submit handler, so this fires when the user skipped the field, or it was cleared by resetForm after switching pages.","commonSituations":"Adding an OpenAI-compatible custom provider and forgetting the endpoint; editing an existing provider whose base URL was cleared; switching provider type in the form leaving the old (now-cleared) URL draft empty.","solutions":["Fill in the provider's API endpoint, e.g. 'https://api.groq.com/openai/v1'.","Verify which field group is visible — the requirement depends on the selected connection type; pick a preset that hosts its own URL if you don't have one.","Re-enter the URL after switching provider types, since resetForm clears the draft."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"function baseUrlSatisfied(required: boolean, input: string): boolean {\n  return !required || input.trim().length > 0;\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Mark the Base URL field required (*) for provider types that need it and disable Save while empty.","Re-populate the field when editing an existing provider so users don't accidentally clear it.","Choose a hosted preset (which owns its base_url) when you don't manage an endpoint yourself."],"tags":["validation","url","providers","frontend","form"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}