From 5bf3a56e277dd472cd689e95d826dee99d4192fc Mon Sep 17 00:00:00 2001 From: Jason Hoku Date: Mon, 18 May 2026 16:00:34 -0700 Subject: [PATCH] fix: detect missing checkpoint file and surface clear error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User report on a different machine: 'AttributeError: NoneType has no attribute lower' at comfy/utils.py:126 inside load_torch_file. Root cause: manifest's meta.model references a checkpoint that isn't installed on this machine — folder_paths.get_full_path returns None, that None propagates through load_checkpoint_guess_config to load_torch_file, crashes cryptically. Two defensive checks: 1. upscale_runner._run_upscale_thread: validates the session checkpoint resolves to a real file BEFORE calling load_checkpoint. If not, sets job['error'] with a clear actionable message ('Copy the .safetensors file over, or regenerate the images on this machine') and exits early. 2. florence2_hires._get_or_load_checkpoint_lora: same check before the fresh-load path (different per-item model). On missing file, falls back to the session base with a one-line log so the upscale still completes instead of crashing the whole job. Tests: added folder_paths.get_full_path to the root conftest stub returning a synthetic path so mocked-loader tests don't trip the new file-check. 96/96 still passing. --- conftest.py | 6 ++++++ florence2_hires.py | 20 +++++++++++++++++++- upscale_runner.py | 23 ++++++++++++++++++++++- 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/conftest.py b/conftest.py index f4e11ee..f4be30e 100644 --- a/conftest.py +++ b/conftest.py @@ -58,6 +58,12 @@ def _install_stubs(): fp = sys.modules["folder_paths"] if not hasattr(fp, "get_output_directory"): fp.get_output_directory = lambda: os.path.join(_NODE_ROOT, "_test_output") # type: ignore + if not hasattr(fp, "get_full_path"): + # Stub returns a synthetic path for any (folder, name) lookup so tests + # that don't care about file existence (e.g. mocked loaders) don't trip + # the "file not found -> fall back" guard in florence2_hires. Tests that + # specifically want to exercise the missing-file path can monkeypatch. + fp.get_full_path = lambda folder, name: f"/{folder}/{name}" if name else None # type: ignore # server.PromptServer if "server" not in sys.modules: diff --git a/florence2_hires.py b/florence2_hires.py index 3316454..757c189 100644 --- a/florence2_hires.py +++ b/florence2_hires.py @@ -401,7 +401,25 @@ def _get_or_load_checkpoint_lora(item, fallback, model_source, cache, session_mo cache[key] = result return result - # Different model — full load. + # Different model — full load. Validate the file exists first; without this + # guard a missing checkpoint hits comfy/utils.load_torch_file(None,...) and + # crashes with "'NoneType' has no attribute 'lower'". Common scenario: manifest + # was generated on a different machine that had the per-item checkpoint + # installed. + try: + import folder_paths as _fp_check + ckpt_path_check = _fp_check.get_full_path("checkpoints", model_name) + except Exception: + ckpt_path_check = None + if ckpt_path_check is None: + print( + f"[Florence2HiResFix] Item references model '{model_name}' but the " + f"file is not installed in ComfyUI/models/checkpoints/. Falling back to " + f"session default." + ) + cache[key] = fallback + return fallback + try: model, clip, vae = ckpt_fn( target_model_name=model_name, diff --git a/upscale_runner.py b/upscale_runner.py index 8b433d2..8b345c7 100644 --- a/upscale_runner.py +++ b/upscale_runner.py @@ -124,9 +124,30 @@ def _run_upscale_thread(job, target_items, upscale_config, meta, manifest_data, _ext = "jpg" if _fmt in ("jpg", "jpeg") else _fmt # Load model, VAE, CLIP for conditioning - model_name = meta.get("model", "") + model_name = meta.get("model", "") or "" vae_name = meta.get("vae", "") + # Validate the checkpoint exists before calling load_checkpoint. Without + # this guard, a missing file ends up at comfy/utils.py:load_torch_file(None, + # ...) and crashes with the cryptic "'NoneType' object has no attribute + # 'lower'". Common cause: manifest copied from another machine where the + # checkpoint isn't installed; or manifest entry has model=None. + try: + import folder_paths as _fp_check + ckpt_path_check = _fp_check.get_full_path("checkpoints", model_name) if model_name else None + except Exception: + ckpt_path_check = None + if not model_name or ckpt_path_check is None: + job["status"] = "error" + job["error"] = ( + f"Checkpoint '{model_name or '(empty)'}' not found in " + f"ComfyUI/models/checkpoints/. The session manifest references it but " + f"the file is not installed on this machine. Copy the .safetensors " + f"file over, or regenerate the images on this machine first." + ) + print(f"[DashboardUpscale] ❌ {job['error']}") + return + # VRAM hygiene before load_checkpoint: the previous workflow's model handles # may still be Python-referenced and won't be evicted by ComfyUI's LRU on a # new load_checkpoint call. soft_empty_cache + gc.collect releases those