Spaces:
Running on Zero
fix(spaces): three runtime fixes — input staging dir, walker, cache perms
Browse files1. _COMFY_INPUT_DIR was hardcoded to <repo>/comfyui/input — fine locally
but on Spaces ComfyUI lives at ~/comfyui, so user uploads were being
staged to a directory ComfyUI never reads from. Mirror the bootstrap
logic so the staging dir matches comfy_dir on each platform.
2. walk_workflow_for_models() was returning every filename mentioned in
Power Lora Loader rows, including those with on:false. ensure_models
then tried to download those (camera LoRAs) at runtime which failed
noisily and pointlessly. Skip rows whose on flag is falsy.
3. After preload_from_hub bakes ~111 GB into ~/.cache/huggingface/, that
directory tree's permissions can block runtime hf_hub_download writes
(xet log dir, blob targets). chmod -R u+rwX in bootstrap so any model
NOT covered by preload (the GGUF, conditional LoRAs) can still
lazy-download. Failure to chmod is non-fatal.
|
@@ -95,6 +95,25 @@ def _bootstrap() -> None:
|
|
| 95 |
sys.path.insert(0, str(comfy_dir))
|
| 96 |
os.environ.setdefault("COMFY_MODELS_DIR", str(comfy_dir / "models"))
|
| 97 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
# Stage placeholder input files so the workflow's hard-referenced loaders
|
| 99 |
# (LoadImage/VHS_Load*) don't error at runtime even when the active mode
|
| 100 |
# doesn't actually use the file. Real user uploads are placed alongside via
|
|
@@ -595,7 +614,13 @@ def _get_backend() -> backend_module.ComfyUILibraryBackend:
|
|
| 595 |
return _BACKEND
|
| 596 |
|
| 597 |
|
| 598 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 599 |
|
| 600 |
|
| 601 |
def _stage_to_comfy_input(file_path) -> str | None:
|
|
|
|
| 95 |
sys.path.insert(0, str(comfy_dir))
|
| 96 |
os.environ.setdefault("COMFY_MODELS_DIR", str(comfy_dir / "models"))
|
| 97 |
|
| 98 |
+
# Make the HF cache writable: preload_from_hub populates ~/.cache/huggingface
|
| 99 |
+
# during build, which can leave it in a state that blocks runtime
|
| 100 |
+
# hf_hub_download writes (e.g., xet's lockdir, blob targets). chmod -R u+rwX
|
| 101 |
+
# so any model NOT covered by preload (camera LoRAs, conditional GGUF, etc.)
|
| 102 |
+
# can still lazy-download. Failures here are non-fatal.
|
| 103 |
+
if on_spaces:
|
| 104 |
+
import subprocess
|
| 105 |
+
|
| 106 |
+
hf_cache = pathlib.Path.home() / ".cache" / "huggingface"
|
| 107 |
+
if hf_cache.exists():
|
| 108 |
+
try:
|
| 109 |
+
subprocess.run(
|
| 110 |
+
["chmod", "-R", "u+rwX", str(hf_cache)],
|
| 111 |
+
check=False,
|
| 112 |
+
timeout=30,
|
| 113 |
+
)
|
| 114 |
+
except Exception as exc:
|
| 115 |
+
print(f"[bootstrap] hf cache chmod skipped: {exc}", flush=True)
|
| 116 |
+
|
| 117 |
# Stage placeholder input files so the workflow's hard-referenced loaders
|
| 118 |
# (LoadImage/VHS_Load*) don't error at runtime even when the active mode
|
| 119 |
# doesn't actually use the file. Real user uploads are placed alongside via
|
|
|
|
| 614 |
return _BACKEND
|
| 615 |
|
| 616 |
|
| 617 |
+
# Must match the comfy_dir used in _bootstrap() — on Spaces this is
|
| 618 |
+
# ~/comfyui (mirroring backend.py's _comfy_dir), otherwise repo-local.
|
| 619 |
+
_COMFY_INPUT_DIR = (
|
| 620 |
+
(pathlib.Path.home() / "comfyui" / "input")
|
| 621 |
+
if _on_spaces()
|
| 622 |
+
else pathlib.Path(__file__).parent / "comfyui" / "input"
|
| 623 |
+
)
|
| 624 |
|
| 625 |
|
| 626 |
def _stage_to_comfy_input(file_path) -> str | None:
|
|
@@ -177,11 +177,17 @@ def _walk_for_filenames(value, into: set[str]) -> None:
|
|
| 177 |
Power Lora Loader stores its rows nested as `inputs.lora_1 = {on, lora,
|
| 178 |
strength}` and similar — a flat values() loop misses these. Recurse
|
| 179 |
through dicts and lists/tuples so nested filenames are caught.
|
|
|
|
|
|
|
|
|
|
| 180 |
"""
|
| 181 |
if isinstance(value, str):
|
| 182 |
if value.endswith(_MODEL_EXTS) or value == "tokenizer.model":
|
| 183 |
into.add(value)
|
| 184 |
elif isinstance(value, dict):
|
|
|
|
|
|
|
|
|
|
| 185 |
for v in value.values():
|
| 186 |
_walk_for_filenames(v, into)
|
| 187 |
elif isinstance(value, (list, tuple)):
|
|
|
|
| 177 |
Power Lora Loader stores its rows nested as `inputs.lora_1 = {on, lora,
|
| 178 |
strength}` and similar — a flat values() loop misses these. Recurse
|
| 179 |
through dicts and lists/tuples so nested filenames are caught.
|
| 180 |
+
|
| 181 |
+
Skips Power Lora Loader rows with `on: false` — those LoRAs aren't
|
| 182 |
+
actually loaded at runtime so there's no point downloading them.
|
| 183 |
"""
|
| 184 |
if isinstance(value, str):
|
| 185 |
if value.endswith(_MODEL_EXTS) or value == "tokenizer.model":
|
| 186 |
into.add(value)
|
| 187 |
elif isinstance(value, dict):
|
| 188 |
+
# Power Lora Loader row: {"on": bool, "lora": "...", "strength": ...}
|
| 189 |
+
if "on" in value and "lora" in value and not value.get("on"):
|
| 190 |
+
return
|
| 191 |
for v in value.values():
|
| 192 |
_walk_for_filenames(v, into)
|
| 193 |
elif isinstance(value, (list, tuple)):
|