{"record":{"id":"c79e4fc32dd0093c","repo":"unslothai/unsloth","slug":"config-output-dir-is-the-outputs-folder-its","errorCode":null,"errorMessage":"'{config['output_dir']}' is the outputs folder itself, not a run inside it. Pick a name for this run.","messagePattern":"'(.+?)' is the outputs folder itself, not a run inside it\\. Pick a name for this run\\.","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"warning","filePath":"studio/backend/routes/training.py","lineNumber":2739,"sourceCode":"                    \"Stop it before starting diffusion (Images) training.\"\n                ),\n            )\n    except HTTPException:\n        raise\n    except Exception:  # noqa: BLE001 -- backend import/health issue must not block a start\n        pass\n\n    # Resolve + contain the dataset and output paths BEFORE spawning: the trainer subprocess would otherwise resolve them relative to its own cwd.\n    config = body.model_dump()\n    try:\n        from utils.paths import outputs_root, resolve_output_dir\n\n        config[\"data_dir\"] = str(_resolve_diffusion_data_dir(config[\"data_dir\"]))\n        # A name that cleans away to nothing (\".\" / \"outputs\" / \"./.\") resolves to the outputs ROOT, where the trainer would write the adapter flat and the is_dir()-filtered listings could never see it.\n        root = outputs_root().resolve()\n        out_dir = resolve_output_dir(config[\"output_dir\"])\n        if Path(out_dir).resolve() == root:\n            raise HTTPException(\n                status_code = 400,\n                detail = (\n                    f\"'{config['output_dir']}' is the outputs folder itself, not a run inside it. \"\n                    \"Pick a name for this run.\"\n                ),\n            )\n        config[\"output_dir\"] = str(out_dir)\n        # The persistent conditioning cache is another trainer-written directory, so it gets the same containment. Blank/None means the in-memory cache, so it must not resolve to the outputs root.\n        cond_cache = str(config.get(\"cond_cache_dir\") or \"\").strip()\n        cond_cache_dir = resolve_output_dir(cond_cache) if cond_cache else None\n        # Same collapse, but the cache has an honest \"off\" to fall back to: one flat safetensors per cached latent in the trained-models directory is never what was meant.\n        if cond_cache_dir is not None and Path(cond_cache_dir).resolve() == root:\n            cond_cache_dir = None\n        config[\"cond_cache_dir\"] = str(cond_cache_dir) if cond_cache_dir is not None else None\n    except ValueError as e:\n        raise HTTPException(status_code = 400, detail = str(e))\n\n    # Validate the config BEFORE freeing resident GPU workloads, so a refused start never tears down the user's chat/Images model. service.start() re-runs this before spawn.","sourceCodeStart":2721,"sourceCodeEnd":2757,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/studio/backend/routes/training.py#L2721-L2757","documentation":"HTTP 400 on diffusion training start: the requested output_dir name sanitizes/resolves to the outputs ROOT directory itself (e.g. '.' or 'outputs' or './.'), not a run directory inside it. Writing there would place the adapter flat in the outputs folder where the is_dir()-filtered listings could never display it. The route compares Path(resolve_output_dir(output_dir)).resolve() against outputs_root().resolve() and rejects equality before spawning.","triggerScenarios":"POST diffusion training start with output_dir='.', 'outputs', './.', or any name that cleans away to the outputs root.","commonSituations":"Clients defaulting output_dir to '.' when the user leaves the run-name field blank; hand-written config files with an empty or placeholder output name.","solutions":["Provide a real run name, e.g. output_dir='my-sdxl-run-1'.","In UI/client code, treat an empty run name as a form error instead of sending '.'.","Never pass the literal outputs root path as output_dir."],"exampleFix":"// before\n{\"output_dir\": \".\", ...}\n// after\n{\"output_dir\": \"sdxl-lora-run-1\", ...}","handlingStrategy":"validation","validationCode":"function validRunName(name) {\n  const cleaned = (name ?? '').trim().replace(/\\\\.[\\\\/]+/g, '')\n  return cleaned !== '' && cleaned !== '.' && cleaned !== 'outputs' && !name.startsWith('/')\n}\nif (!validRunName(payload.output_dir)) throw new Error('Pick a real run name (not \".\" or \"outputs\")')","typeGuard":null,"tryCatchPattern":"try { await startDiffusionTraining(payload) } catch (e) { if (e.status === 400 && /outputs folder itself/.test(e.detail)) { payload.output_dir = `run-${Date.now()}`; return startDiffusionTraining(payload) } throw e }","preventionTips":["Require a non-empty run name in the UI form; never default to '.'.","Treat output_dir as a run NAME, not a filesystem path.","Reject '.', 'outputs', and empty values client-side before sending."],"tags":["diffusion","output-dir","paths","validation","http-400"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}