diff --git a/src/gpu_rent/remote/install_swarm_comfy.py b/src/gpu_rent/remote/install_swarm_comfy.py index dc54ae9..f5a8186 100644 --- a/src/gpu_rent/remote/install_swarm_comfy.py +++ b/src/gpu_rent/remote/install_swarm_comfy.py @@ -263,10 +263,12 @@ def recover_errored_backends(*, wait_sec: float = 180.0) -> str: return "errored" else: for bid, meta in entries: - script = str((meta.get("settings") or {}).get("StartScript") or "") + settings = meta.get("settings") or {} + script = str(settings.get("StartScript") or "") + extra = str(settings.get("ExtraArgs") or "") print(f"reconfigure errored id={bid} old StartScript={script!r}", flush=True) try: - configure_comfy_backend(sid, bid) + configure_comfy_backend(sid, bid, extra_args=extra) except Exception as exc: print(f"configure FAIL id={bid}: {exc}", flush=True) @@ -328,7 +330,11 @@ def recover_errored_backends(*, wait_sec: float = 180.0) -> str: def sanitize_backends_fds() -> bool: - """Repair ExtraArgs: \\x --flag (FDS empty + appended flag). Return True if changed.""" + """Repair ExtraArgs: \\x --flag (FDS empty + appended flag). Return True if changed. + + Do not use ``\\s`` after ``\\x`` — newline would be eaten and merge FDS keys. + Bare ``ExtraArgs: \\x`` is valid empty and must be left alone. + """ path = DATA / "Data" / "Backends.fds" if not path.is_file(): return False @@ -336,8 +342,8 @@ def sanitize_backends_fds() -> bool: text = path.read_text(encoding="utf-8") except OSError: return False - new, n = re.subn(r"^(\s*ExtraArgs:\s*)\\x(\s+)", r"\1", text, flags=re.M) - if not n: + new, n = re.subn(r"^(\s*ExtraArgs:\s*)\\x[ \t]+", r"\1", text, flags=re.M) + if not n or new == text: return False path.write_text(new, encoding="utf-8") print(f"sanitized Backends.fds ExtraArgs \\x corruption ({n} line(s))", flush=True) @@ -449,12 +455,18 @@ def toggle_backend(sid: str, backend_id: int, *, enabled: bool) -> dict: ) -def configure_comfy_backend(sid: str, backend_id: int) -> None: +def configure_comfy_backend( + sid: str, backend_id: int, *, extra_args: str | None = None +) -> None: """Set StartScript after AddNewBackend (defaults are empty → stays disabled).""" script = comfy_start_script() + # Preserve ExtraArgs (e.g. --use-sage-attention); "" wipes sage every recover. + extra = "" if extra_args is None else str(extra_args).strip() + if extra in {"\\x", "x"}: + extra = "" settings = { "StartScript": script, - "ExtraArgs": "", + "ExtraArgs": extra, "DisableInternalArgs": False, "AutoUpdate": "false", "UpdateManagedNodes": "false", @@ -551,7 +563,8 @@ def recover_empty_backends() -> str: flush=True, ) try: - configure_comfy_backend(sid, bid) + extra = str(settings.get("ExtraArgs") or "") + configure_comfy_backend(sid, bid, extra_args=extra) except Exception as exc: print(f"configure FAIL id={bid}: {exc}", flush=True) diff --git a/src/gpu_rent/remote/tune_swarm_perf.py b/src/gpu_rent/remote/tune_swarm_perf.py index c16e1ba..0bab583 100644 --- a/src/gpu_rent/remote/tune_swarm_perf.py +++ b/src/gpu_rent/remote/tune_swarm_perf.py @@ -84,26 +84,21 @@ def find_pip() -> Path | None: def sanitize_backends_fds() -> bool: - """Repair ``ExtraArgs: \\x --flag`` and bare ``ExtraArgs: \\x`` → clean values.""" + """Repair ``ExtraArgs: \\x --flag`` only. Bare ``\\x`` is valid FDS empty. + + Important: do not use ``\\s+`` after ``\\x`` — that eats the newline and + merges the next FDS key onto the ExtraArgs line (breaks SwarmUI load). + """ if not BACKENDS.is_file(): return False text = BACKENDS.read_text(encoding="utf-8") - new = text - # ``ExtraArgs: \x --flags`` → ``ExtraArgs: --flags`` - new, n1 = re.subn( - r"^(\s*ExtraArgs:\s*)\\x(\s+)", + # Only horizontal whitespace after \x (spaces/tabs), never newline. + new, n = re.subn( + r"^(\s*ExtraArgs:\s*)\\x[ \t]+", r"\1", - new, + text, flags=re.M, ) - # Bare empty marker left alone is OK for Swarm, but normalize trailing junk. - new, n2 = re.subn( - r"^(\s*ExtraArgs:\s*)\\x\s*$", - r"\1", - new, - flags=re.M, - ) - n = n1 + n2 if n and new != text: BACKENDS.write_text(new, encoding="utf-8") print(f"sanitized Backends.fds ExtraArgs \\x corruption ({n} line(s))") diff --git a/tests/test_tune_swarm_perf.py b/tests/test_tune_swarm_perf.py index 09f68ab..ca72bd7 100644 --- a/tests/test_tune_swarm_perf.py +++ b/tests/test_tune_swarm_perf.py @@ -178,6 +178,17 @@ def test_sanitize_backends_fds_corruption(tmp_path, monkeypatch): assert backends.read_text(encoding="utf-8") == "ExtraArgs: --use-sage-attention\n" +def test_sanitize_does_not_eat_newline_on_bare_empty(tmp_path, monkeypatch): + """Bare FDS empty ``\\x`` must not match — ``\\s+`` would merge next key.""" + mod = _load() + backends = tmp_path / "Backends.fds" + original = "\tExtraArgs: \\x\n\tStartScript: /mnt/swarm_data/dlbackend/ComfyUI/main.py\n" + backends.write_text(original, encoding="utf-8") + monkeypatch.setattr(mod, "BACKENDS", backends) + assert mod.sanitize_backends_fds() is False + assert backends.read_text(encoding="utf-8") == original + + def test_ensure_absolute_start_script(tmp_path, monkeypatch): mod = _load() data = tmp_path