calesthio/OpenMontage · error · ComfyUIError

No output artifacts on node

Error message

No output artifacts on node {output_node}. Available nodes: {list(outputs.keys())}

What it means

ComfyUIError raised during artifact download when the history entry's outputs contain no items for the requested output_node under any of the artifact keys ComfyUI uses ('images', 'gifs', 'audio', 'video'). The message lists the node ids that DO have outputs, which is the key diagnostic: usually the caller asked for a preview/output node id different from the node that actually saved files.

Solutions

  1. Read the 'Available nodes' list in the message and retry with the id that actually holds outputs (the Save* node)
  2. Open the workflow JSON and confirm the save node's id; pass it as output_node (or via the tool's output_node parameter)
  3. If using community nodes whose output key differs (e.g. VHS_VideoCombine), pass the node id of a standard-output node or extend the key list
  4. Ensure the save node is not muted/bypassed (mode 2/4 in UI exports)

Example fix

# before
client.download_output(entry, output_node="3", dest=...)  # KSampler id

# after
client.download_output(entry, output_node="9", dest=...)  # SaveImage id from message's Available nodes
Defensive patterns

Strategy: validation

Validate before calling

# derive a save-node id from the workflow instead of guessing
def find_save_node(workflow: dict) -> str | None:
    for nid, node in workflow.items():
        if node.get("class_type", "").startswith(("Save", "VHS_")):
            return nid
    return None

output_node = output_node or find_save_node(workflow)
if output_node is None:
    raise SystemExit("workflow has no save node; cannot collect artifacts")

Type guard

def node_has_outputs(entry: dict, node_id: str) -> bool:
    out = entry.get("outputs", {}).get(node_id, {})
    return bool(out.get("images") or out.get("gifs") or out.get("audio") or out.get("video"))

Try / catch

try:
    client.download_output(entry, output_node, dest)
except ComfyUIError as e:
    if "No output artifacts" in str(e):
        # message lists Available nodes — pick the Save* id and retry
        available = list(entry.get("outputs", {}).keys())
        retry_node = next((n for n in available if "save" in str(entry['outputs'][n]).lower()), available[0])
        client.download_output(entry, retry_node, dest)
    else:
        raise

Prevention

When it happens

Trigger: Calling the download/collect method with an output_node id that produced no artifacts (e.g. a KSampler node instead of the SaveImage node), or pointing at a PreviewImage whose outputs were consumed; also when the real save node wrote under an unexpected key.

Common situations: Custom workflows with multiple save nodes where the caller guessed the wrong id; workflow re-exported and node ids renumbered; SaveAnimatedWEBP/VHS nodes storing outputs under keys not in the checked list; execution saved zero files because a bypassed node was used.

Related errors


AI-assisted analysis of calesthio/OpenMontage@95e1c3d0ab (2026-08-15). Data as JSON: /api/errors/fd8716706102cc48. Report an issue: GitHub.

Appendix: source

Thrown at tools/_comfyui/client.py:456

            entry = self.poll(prompt_id, timeout=timeout, interval=interval)
        else:
            entry = self._wait(
                prompt_id, timeout=timeout, interval=interval, on_progress=on_progress
            )

        outputs = entry.get("outputs", {})
        node_output = outputs.get(output_node, {})

        # ComfyUI stores images/video frames under "images", legacy GIFs
        # under "gifs", and the native SaveAudio node's output under "audio".
        items = (
            node_output.get("images", [])
            or node_output.get("gifs", [])
            or node_output.get("audio", [])
            or node_output.get("video", [])
        )
        if not items:
            raise ComfyUIError(
                f"No output artifacts on node {output_node}. "
                f"Available nodes: {list(outputs.keys())}"
            )

        paths: list[Path] = []
        for i, item in enumerate(items):
            suffix = Path(item["filename"]).suffix
            if len(items) == 1:
                target = dest
            else:
                target = dest.with_stem(f"{dest.stem}_{i:03d}").with_suffix(suffix)
            self.download(
                item["filename"],
                item.get("subfolder", ""),
                target,
                item.get("type", "output"),
            )
            paths.append(target)

View on GitHub (pinned to 95e1c3d0ab)