{"record":{"id":"a5c415a1c57a689e","repo":"unslothai/unsloth","slug":"controlnet-spec-id-is-no-longer-present-on-dis","errorCode":null,"errorMessage":"ControlNet '{spec_id}' is no longer present on disk","messagePattern":"ControlNet '(.+?)' is no longer present on disk","errorType":"validation","errorClass":"FileNotFoundError","httpStatus":400,"severity":"error","filePath":"studio/backend/core/inference/diffusion_controlnet.py","lineNumber":178,"sourceCode":"    ``family`` enforces compatibility: a ControlNet is architecture-specific, so an entry tagged\n    for another family is rejected here rather than loaded through the wrong pipeline later.\n    \"\"\"\n    entry = _catalog_by_id().get(spec_id)\n    if entry is None:\n        # A curated entry named by its full repo id must still hit the family gate below, not slip through the bare-repo fallback.\n        entry = next((e for e in _CURATED if e.repo_id and e.repo_id == spec_id), None)\n    if entry is not None:\n        # A direct API call could send an entry for another family; reject it before any download.\n        fam = (family or \"\").strip().lower()\n        if entry.families and fam and fam not in {f.lower() for f in entry.families}:\n            raise ValueError(\n                f\"ControlNet '{spec_id}' is for {', '.join(entry.families)}, not the loaded \"\n                f\"'{family}' model; pick a ControlNet built for this family.\"\n            )\n        if entry.source == \"local\":\n            path = entry.local_path or \"\"\n            if not path or not Path(path).is_dir():\n                raise FileNotFoundError(f\"ControlNet '{spec_id}' is no longer present on disk\")\n            return ResolvedControlNet(spec_id, path, is_local = True)\n        if not entry.repo_id:\n            raise ValueError(f\"ControlNet '{spec_id}' has no repo\")\n        return ResolvedControlNet(spec_id, entry.repo_id, is_local = False)\n\n    # A bare HF repo id (owner/name). STRICT shape so a filesystem-looking id can never reach from_pretrained.\n    if re.fullmatch(r\"[A-Za-z0-9][A-Za-z0-9_.-]*/[A-Za-z0-9][A-Za-z0-9_.-]*\", spec_id):\n        return ResolvedControlNet(spec_id, spec_id, is_local = False)\n\n    raise FileNotFoundError(\n        f\"unknown ControlNet '{spec_id}': not a local model, catalog entry, or HF repo id\"\n    )\n\n\n# Union ControlNet mode indices: a union model selects its head via an integer ``control_mode``.\n_UNION_CONTROL_MODES: dict[str, int] = {\n    \"canny\": 0,\n    \"tile\": 1,","sourceCodeStart":160,"sourceCodeEnd":196,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/studio/backend/core/inference/diffusion_controlnet.py#L160-L196","documentation":"resolve_controlnet() found a catalog entry with source='local' (discovered by scanning a models directory), but the entry's local_path is empty or no longer a directory on disk. The scan that built the entry and the resolve can happen at different times, so the folder may have been deleted, renamed, or moved in between.","triggerScenarios":"Calling resolve_controlnet(spec_id) where spec_id is a folder name previously found under the local ControlNet models directory, after that folder was deleted, renamed, or made unreadable; or a hand-crafted ControlNetCatalogEntry(source='local', local_path='') / with a path on a disconnected drive.","commonSituations":"User deletes a ControlNet folder via the OS while the studio backend is running and the UI still shows the stale catalog entry; models stored on an external/network drive that unmounted; folder renamed to reorganize models.","solutions":["Restore or re-create the ControlNet folder at the recorded local_path (must contain config.json plus loadable weights)","Re-list the catalog (list_controlnets) to refresh the directory scan and pick a currently-present entry","If the model is gone for good, switch to a curated/remote entry or a bare 'owner/name' Hugging Face repo id","API authors: catch FileNotFoundError from resolve_controlnet and map it to a 400 with this message for the client"],"exampleFix":"// before\nresolved = resolve_controlnet(\"my-local-cnet\")\n\n// after\nfrom pathlib import Path\nentry = _catalog_by_id().get(\"my-local-cnet\")\nif entry is None or entry.source == \"local\" and not Path(entry.local_path or \"\").is_dir():\n    entries = list_controlnets()  # refresh scan, pick a live entry\nresolved = resolve_controlnet(entries[0].id)","handlingStrategy":"try-catch","validationCode":"from pathlib import Path\nfrom studio.backend.core.inference.diffusion_controlnet import list_controlnets\n\ndef controlnet_loadable(spec_id: str) -> bool:\n    return any(\n        e.id == spec_id and (e.source != \"local\" or Path(e.local_path or \"\").is_dir())\n        for e in list_controlnets()\n    )","typeGuard":null,"tryCatchPattern":"try:\n    resolved = resolve_controlnet(spec_id, family=fam)\nexcept FileNotFoundError as e:\n    # catalog entry stale or id unknown -> 400 for the client, never a 500\n    raise HTTPBadRequest(str(e)) from e","preventionTips":["Refresh the catalog with list_controlnets() before showing choices in the UI so deleted folders drop out","Keep local ControlNet folders (config.json + weights) inside the scanned models directory and avoid renaming them while the backend runs","Treat FileNotFoundError from resolve_controlnet as a client error (400), not a server fault"],"tags":["controlnet","filesystem","stale-state","diffusion"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}