From f0197f06c37f6b8aa2b138c69fea998103286bb0 Mon Sep 17 00:00:00 2001 From: perfectmodelslab Date: Sun, 25 Oct 2026 12:06:03 +0100 Subject: [PATCH 1/2] launch --background: honor --log-file and COMFY_CLI_BACKGROUND_LOG for the log path background_launch hardcodes the log to /user/comfyui_.log. comfy-cli is increasingly started by a supervisor (project launchers, systemd units) whose other services log where the supervisor says, not where the workspace says (GitHub #692). --log-file (and the COMFY_CLI_BACKGROUND_LOG env var for children comfy-cli cannot be edited to pass flags) replaces the path verbatim: a supervisor that names the file owns the naming too, so the suffix logic does not apply. The chosen path is recorded under CONFIG_KEY_BACKGROUND_LOG, so comfy logs and comfy stop resolve it like any other background log. Tests pin the threading, the override, the env var and the unchanged default. --- README.md | 1 + comfy_cli/cmdline.py | 15 ++++++++- comfy_cli/command/launch.py | 23 +++++++++---- .../command/test_launch_background.py | 32 +++++++++++++++++++ 4 files changed, 64 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 614e7683a..eaaffe0c4 100644 --- a/README.md +++ b/README.md @@ -200,6 +200,7 @@ Comfy provides commands that allow you to easily run the installed ComfyUI. - Instances launched with `--background` are displayed in the "Background ComfyUI" section of `comfy env`, providing management functionalities for a single background instance only. - Background-running ComfyUI can be stopped with `comfy stop`. + - Redirect the background log with `comfy launch --background --log-file /tmp/comfy.log` (or the `COMFY_CLI_BACKGROUND_LOG` env var), so supervising scripts keep it next to their own logs. - To point **every** command (not just one) at a ComfyUI on a non-default address — e.g. a server you started _outside_ comfy-cli on `:8189` — export diff --git a/comfy_cli/cmdline.py b/comfy_cli/cmdline.py index 04cf0ea63..b7dd5086a 100644 --- a/comfy_cli/cmdline.py +++ b/comfy_cli/cmdline.py @@ -2565,10 +2565,23 @@ def launch( help="Use a specific frontend PR. Supports formats: username:branch, #123, or PR URL", ), ] = None, + log_file: Annotated[ + str | None, + typer.Option( + "--log-file", + show_default=False, + help=( + "Where the background server's log is written instead of " + "/user/comfyui_.log. Only used with --background. " + "The COMFY_CLI_BACKGROUND_LOG env var does the same for processes " + "comfy-cli cannot be edited to pass flags." + ), + ), + ] = None, ): from comfy_cli.command.launch import launch as launch_command - launch_command(background, extra, frontend_pr) + launch_command(background, extra, frontend_pr, log_file=log_file) @app.command(help="Show a captured ComfyUI log (from `comfy launch --background`, or ComfyUI-Manager's own logfile).") diff --git a/comfy_cli/command/launch.py b/comfy_cli/command/launch.py index efafccd9f..0dd15cf8b 100644 --- a/comfy_cli/command/launch.py +++ b/comfy_cli/command/launch.py @@ -411,6 +411,7 @@ def launch( background: bool = False, extra: list[str] | None = None, frontend_pr: str | None = None, + log_file: str | None = None, ): resolved_workspace = workspace_manager.workspace_path @@ -451,12 +452,12 @@ def launch( extra = (extra or []) + manager_flags if background: - background_launch(extra, frontend_pr) + background_launch(extra, frontend_pr, log_file=log_file) else: launch_comfyui(extra, frontend_pr, python=python) -def background_launch(extra, frontend_pr=None): +def background_launch(extra, frontend_pr=None, log_file=None): renderer = get_renderer() config_background = ConfigManager().background if config_background is not None and utils.is_running(config_background[2]): @@ -538,7 +539,7 @@ def background_launch(extra, frontend_pr=None): cmd.extend(extra) - log = asyncio.run(launch_and_monitor(cmd, listen, port)) + log = asyncio.run(launch_and_monitor(cmd, listen, port, log_file=log_file)) # Reaching here means the monitor returned without seeing the success line # (the success path emits its envelope and _hard_exit(0)s inside the monitor). @@ -574,7 +575,7 @@ def background_launch(extra, frontend_pr=None): _hard_exit(1) -def background_log_path(port, workspace: str | None = None) -> str: +def background_log_path(port, workspace: str | None = None, override: str | None = None) -> str: """Path to the persisted background ComfyUI log for ``port``. ``/user/comfyui_.log`` — ```` disambiguates multiple @@ -582,7 +583,17 @@ def background_log_path(port, workspace: str | None = None) -> str: (a fresh run starts a fresh log). ``workspace`` defaults to the current working directory, which ``launch`` has already ``chdir``'d to the resolved workspace by the time the background monitor runs. + + ``override`` (the ``--log-file`` flag, or the ``COMFY_CLI_BACKGROUND_LOG`` + env var a supervising process can set for its own children) replaces the + path verbatim: a supervisor that names the file owns the naming too, so the + ```` suffix logic does not apply. The chosen path is what gets recorded + under ``CONFIG_KEY_BACKGROUND_LOG``, so ``comfy logs`` and ``comfy stop`` + resolve it like any other background log. """ + override = override or os.environ.get("COMFY_CLI_BACKGROUND_LOG") + if override: + return os.path.abspath(override) if workspace is None: workspace = os.path.abspath(os.getcwd()) return os.path.join(workspace, "user", f"comfyui_{port}.log") @@ -604,7 +615,7 @@ def _open_log_for_write(log_path: str): return os.fdopen(fd, "w", encoding="utf-8") -async def launch_and_monitor(cmd, listen, port): +async def launch_and_monitor(cmd, listen, port, log_file=None): """ Monitor the process during the background launch. @@ -628,7 +639,7 @@ async def launch_and_monitor(cmd, listen, port): # promptly instead of sitting in a block buffer (stdout is a file, not a tty). env["PYTHONUNBUFFERED"] = "1" - log_path = background_log_path(port) + log_path = background_log_path(port, override=log_file) # Truncate-on-launch: each background launch starts a fresh log. The child # inherits its own dup of this fd, so writes continue after we (the monitor) diff --git a/tests/comfy_cli/command/test_launch_background.py b/tests/comfy_cli/command/test_launch_background.py index aa2ea2967..6300e5068 100644 --- a/tests/comfy_cli/command/test_launch_background.py +++ b/tests/comfy_cli/command/test_launch_background.py @@ -8,6 +8,8 @@ from unittest.mock import AsyncMock, patch +import os + import pytest from comfy_cli.caller import Caller @@ -167,3 +169,33 @@ def test_background_launch_refuses_when_already_running(mock_config_manager, moc with pytest.raises(typer.Exit): launch.background_launch(extra=[]) + + +@patch("comfy_cli.command.launch.os._exit") +@patch("comfy_cli.command.launch.launch_and_monitor", new_callable=AsyncMock) +@patch("comfy_cli.command.launch.check_comfy_server_running", return_value=False) +@patch("comfy_cli.command.launch.ConfigManager") +def test_background_launch_threads_log_file_to_monitor( + mock_config_manager, mock_check_running, mock_monitor, mock_exit +): + """#692: --log-file rides down to the monitor, which truncates and records + that path instead of the workspace default.""" + mock_config_manager.return_value.background = None + mock_monitor.return_value = None + + launch.background_launch(extra=[], log_file="/tmp/supervisor/comfy.log") + + assert mock_monitor.call_args[1]["log_file"] == "/tmp/supervisor/comfy.log" + + +def test_background_log_path_override_env_and_default(tmp_path, monkeypatch): + """#692: --log-file and COMFY_CLI_BACKGROUND_LOG replace the whole path + verbatim; with neither set, the workspace default is unchanged.""" + monkeypatch.delenv("COMFY_CLI_BACKGROUND_LOG", raising=False) + assert launch.background_log_path(8188, str(tmp_path)) == str(tmp_path / "user" / "comfyui_8188.log") + + monkeypatch.setenv("COMFY_CLI_BACKGROUND_LOG", "env-placed.log") + assert launch.background_log_path(8188, str(tmp_path)) == os.path.abspath("env-placed.log") + + monkeypatch.delenv("COMFY_CLI_BACKGROUND_LOG") + assert launch.background_log_path(8188, str(tmp_path), override="given.log") == os.path.abspath("given.log") From ce706b1809ec20e24a3939fe3d20da8c9e18cbc3 Mon Sep 17 00:00:00 2001 From: perfectmodelslab Date: Mon, 26 Oct 2026 11:48:47 +0100 Subject: [PATCH 2/2] review: escape the recorded log path for ConfigParser interpolation A --log-file like /tmp/run%20.log made every later cfg.get of the recorded key raise InterpolationSyntaxError, killing the monitor before the child starts. The path is stored with %% escaping and unescaped on the single read site. --- comfy_cli/command/launch.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/comfy_cli/command/launch.py b/comfy_cli/command/launch.py index 0dd15cf8b..a7d1d27ae 100644 --- a/comfy_cli/command/launch.py +++ b/comfy_cli/command/launch.py @@ -665,8 +665,10 @@ async def launch_and_monitor(cmd, listen, port, log_file=None): # when startup fails before the success marker below (where the running # background info is recorded). A fresh ConfigManager re-reads this on the # success path. + # %% escapes ConfigParser interpolation: a --log-file like + # /tmp/run%20.log would otherwise raise on every later read of the key. cfg = ConfigManager() - cfg.config["DEFAULT"][constants.CONFIG_KEY_BACKGROUND_LOG] = log_path + cfg.config["DEFAULT"][constants.CONFIG_KEY_BACKGROUND_LOG] = log_path.replace("%", "%%") cfg.write_config() try: @@ -703,7 +705,7 @@ def _handle(line): # we add the running background info now that startup succeeded. cfg = ConfigManager() cfg.config["DEFAULT"][constants.CONFIG_KEY_BACKGROUND] = f"{(listen, port, process.pid)}" - cfg.config["DEFAULT"][constants.CONFIG_KEY_BACKGROUND_LOG] = log_path + cfg.config["DEFAULT"][constants.CONFIG_KEY_BACKGROUND_LOG] = log_path.replace("%", "%%") cfg.write_config() _emit_launch_success(listen, port, process.pid) @@ -867,7 +869,7 @@ def candidate_log_paths(port: int | None = None) -> list[tuple[str, str]]: ] cfg = ConfigManager() - recorded = cfg.get(constants.CONFIG_KEY_BACKGROUND_LOG) + recorded = (cfg.get(constants.CONFIG_KEY_BACKGROUND_LOG) or "").replace("%%", "%") or None if not workspace: # Nothing local to prefer it over, and nothing else to check.