From 7259e664efafb1266fb7c26d6fcd5eb3fc1ef224 Mon Sep 17 00:00:00 2001 From: Jedrzej Kosinski Date: Thu, 21 May 2026 14:08:23 -0700 Subject: [PATCH] Defer record_node_startup_error in prestartup error path; add docstrings Buffer prestartup failures into a module-level list inside main.py instead of importing 'nodes' (and therefore 'torch') from within the exception handler. After the normal 'import nodes' line, drain the buffer via nodes.record_node_startup_error so bootstrap order stays deterministic regardless of whether a prestartup script succeeded. Also convert the explanatory '#' comment on the new /node_startup_errors endpoint into a proper docstring and add a docstring to execute_prestartup_script, addressing CodeRabbit's docstring-coverage warning on this PR. Addresses review feedback on PR #13184. Amp-Thread-ID: https://ampcode.com/threads/T-019e2f90-26fe-7048-9855-5ff39d08a3e0 Co-authored-by: Amp --- main.py | 40 ++++++++++++++++++++++++++++++++-------- server.py | 25 ++++++++++++++----------- 2 files changed, 46 insertions(+), 19 deletions(-) diff --git a/main.py b/main.py index cbff9ab40..30909af9a 100644 --- a/main.py +++ b/main.py @@ -136,7 +136,20 @@ def apply_custom_paths(): folder_paths.set_user_directory(user_dir) +# Buffer for prestartup failures. Recorded into `nodes.NODE_STARTUP_ERRORS` +# only AFTER the normal `import nodes` line below, so a failing prestartup +# script never triggers an early `import nodes` (and therefore `import torch`) +# on the error path. +_PRESTARTUP_FAILURES: list[dict] = [] + + def execute_prestartup_script(): + """Run every custom_nodes/*/prestartup_script.py once, before importing nodes. + + Failures are buffered into the module-level ``_PRESTARTUP_FAILURES`` list and + must be flushed via ``record_node_startup_error`` after ``import nodes`` has + happened at its normal bootstrap point. + """ if args.disable_all_custom_nodes and len(args.whitelist_custom_nodes) == 0: return @@ -149,14 +162,15 @@ def execute_prestartup_script(): return True except Exception as e: logging.error(f"Failed to execute startup-script: {script_path} / {e}") - from nodes import record_node_startup_error - record_node_startup_error( - module_path=os.path.dirname(script_path), - source="custom_nodes", - phase="prestartup", - error=e, - tb=traceback.format_exc(), - ) + # Buffer the failure - do NOT `import nodes` here, that would drag + # torch in before the intended bootstrap point. + _PRESTARTUP_FAILURES.append({ + "module_path": os.path.dirname(script_path), + "source": "custom_nodes", + "phase": "prestartup", + "error": e, + "tb": traceback.format_exc(), + }) return False node_paths = folder_paths.get_folder_paths("custom_nodes") @@ -216,6 +230,16 @@ import execution import server from protocol import BinaryEventTypes import nodes + +# Flush any prestartup failures that were buffered before `nodes` was +# importable. Doing this here (rather than from the prestartup error +# handler) keeps the bootstrap order deterministic: `nodes` (and torch) +# import at this single line whether prestartup succeeded or failed. +if _PRESTARTUP_FAILURES: + for _failure in _PRESTARTUP_FAILURES: + nodes.record_node_startup_error(**_failure) + _PRESTARTUP_FAILURES.clear() + import comfy.model_management import comfyui_version import app.logger diff --git a/server.py b/server.py index 544e8398b..fc3b503fc 100644 --- a/server.py +++ b/server.py @@ -767,17 +767,20 @@ class PromptServer(): @routes.get("/node_startup_errors") async def get_node_startup_errors(request): - # Group errors by source so the frontend/Manager can render them - # in distinct sections. `source` is the same string as the - # module_parent used at load time (e.g. "custom_nodes", - # "comfy_extras", "comfy_api_nodes") and is left as a free-form - # string so the contract survives node-source layouts evolving. - # The response only contains source buckets that actually had a - # failure; consumers should not assume any particular set of keys - # is always present. - # - # `module_path` is stripped because the absolute on-disk path is - # internal detail that the frontend has no use for. + """Return startup errors recorded during node loading, grouped by source. + + Group errors by source so the frontend/Manager can render them in + distinct sections. ``source`` is the same string as the + ``module_parent`` used at load time (e.g. ``"custom_nodes"``, + ``"comfy_extras"``, ``"comfy_api_nodes"``) and is left as a + free-form string so the contract survives node-source layouts + evolving. The response only contains source buckets that actually + had a failure; consumers should not assume any particular set of + keys is always present. + + ``module_path`` is stripped because the absolute on-disk path is + internal detail that the frontend has no use for. + """ grouped: dict[str, dict[str, dict]] = {} for entry in nodes.NODE_STARTUP_ERRORS.values(): source = entry.get("source", "custom_nodes")