feat(workflow): agent-first workflow editing — CRDT edit primitives, recipes, offline catalog, cloud run association - #511
feat(workflow): agent-first workflow editing — CRDT edit primitives, recipes, offline catalog, cloud run association#511skishore23 wants to merge 18 commits into
Conversation
…recipes, offline catalog, cloud run association
Consolidates the agent-workflow track into one change on top of main. It gives
agents a structured, convergence-safe way to build and edit ComfyUI workflows,
a reuse model that replaces raw fragments, an offline node catalog so edits and
validation work without a live server, and the cloud plumbing to associate and
run those workflows against Comfy Cloud.
Structured (CRDT-ready) workflow edits
- New workflow_ops.py: convergence-safe op model for graph mutation, with the
converge-or-flag invariant and canonical-form soundness proven in tests.
- New `workflow edit` command surface (workflow_edit.py) over those primitives;
set-widget resolves subgraph promoted inputs the same way it resolves slots.
- edit/recipe commands registered in COMMAND_SCHEMAS for discovery.
Recipes replace fragments
- Parameterized recipes + capture as the reuse path; `foreach` bulk-instantiates
a recipe over N param-sets.
- Legacy fragments un-surfaced from agents; comfy-fragments SKILL removed and
skills docs point at recipes as the supported path.
Offline node catalog + validation
- COMFY_OBJECT_INFO_FILE provides a default offline node catalog, honored in the
shared CQL loader (not per-command) with a cache-first TTL for object_info.
- validate lowers frontend/canvas graphs to API before validating; generate emits
complete node inputs and validate flags missing required inputs.
- Rejected values get actionable feedback: normalize mangled model values and
suggest the nearest COMBO; suggest the real id/address on a not-found edit;
drop the seed control_after_generate marker for partner nodes.
Cloud run association
- `comfy run --workflow-id` associates a cloud job with a workflow.
- Accept a forwarded Bearer token via COMFY_CLOUD_AUTH_TOKEN.
- New `assets library ls` / `ensure`; shared cloud-HTTP helpers extracted to
cloud_http.py.
- Cloud object_info loads route through the offline catalog.
Agentic run ergonomics + perf
- Suppress the detached watcher for agentic callers (COMFY_NO_WATCH / --no-watch).
- Kill the telemetry exit hang and defer heavy imports at startup.
- preview renders headless via bundled ffmpeg.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR adds structured frontend workflow editing and recipes, cloud execution and asset integrations, frontend-to-API validation, object-info caching, dynamic-combo handling, media preview fallbacks, lazy imports, telemetry initialization changes, and updated skills, schemas, and tests. ChangesWorkflow editing and validation
Cloud and runtime integrations
Skills and validation coverage
Sequence Diagram(s)sequenceDiagram
participant CLI
participant WorkflowEdit
participant WorkflowOps
participant Renderer
CLI->>WorkflowEdit: invoke workflow edit command
WorkflowEdit->>WorkflowOps: apply graph operation or recipe
WorkflowOps-->>WorkflowEdit: return workflow and operation metadata
WorkflowEdit->>Renderer: emit structured envelope
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 2✅ Passed checks (2 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
Comment |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
There was a problem hiding this comment.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
comfy_cli/cmdline.py (1)
934-1034: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
validatehas grown past comfortable size; extract the UI→API lowering into a helper.The new object_info-load +
convert_ui_to_apiblock (1003-1034) pushesvalidateto 26 locals / 18 branches / 61 statements per Pylint. A small_ensure_api_format(wf_data, mode, input_path, host, port, renderer)helper (returning the possibly-converted dict, or raising/exiting on failure) would isolate this logic and make bothvalidateand any future reuse (e.g. arun --print-promptstyle pre-check) easier to test in isolation.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@comfy_cli/cmdline.py` around lines 934 - 1034, Extract the non-API workflow lowering block from validate into an _ensure_api_format helper accepting wf_data, mode, input_path, host, port, and renderer. Keep the existing resilient_load_object_info and convert_ui_to_api behavior, including cql_no_graph and conversion_error reporting with typer.Exit on failure, and return the original dict unchanged when is_api_format is true. Replace the inline block in validate with the helper result.Source: Linters/SAST tools
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@comfy_cli/cmdline.py`:
- Around line 810-817: Extract the UI-to-API load and conversion logic currently
handled by validate() into a focused helper, then have validate() call that
helper while retaining its existing validation and tracking behavior. Keep the
helper responsible only for the lowering path and preserve the current inputs,
outputs, and error handling.
In `@comfy_cli/command/assets_library.py`:
- Around line 30-39: Run Ruff formatting on the Python file and ensure the long
Typer option declarations, especially the tags option near the limit definition,
comply with Ruff’s line-length formatting and enabled import-sorting rules;
preserve the existing option behavior and values.
In `@comfy_cli/command/workflow_edit.py`:
- Around line 84-96: Define shared Annotated aliases for the repeated
options—where, input_path, host, port, actor, base_version, and
stdout/in-place—near the command declarations, preserving their existing flags,
help text, and defaults. Replace the duplicated option annotations in
add_node_cmd, set_widget_cmd, connect_cmd, delete_cmd, capture_cmd, apply_cmd,
and foreach_cmd with those aliases.
- Around line 507-521: Update the foreach batch handling around the param_sets
loop and its workflow_edit_invalid exception path so failures after earlier
iterations explicitly surface the paths in written, rather than silently
reporting only the error. Preserve successful atomic writes per file, but
include partial-output details in the error reporting and ensure the caller can
determine which files were already produced.
In `@comfy_cli/skills/comfy/SKILL.md`:
- Around line 399-400: Update the subgraph guidance in the structured-edit
primitives section to state that set-widget supports both flat promoted and
nested subgraph addresses directly. Remove the instruction to use set-slot
nested addresses or decompose for values inside subgraphs, while preserving any
accurate limitations for primitives that do not support subgraph edits.
In `@comfy_cli/workflow_ops.py`:
- Around line 704-1103: Run Ruff formatting on comfy_cli/workflow_ops.py lines
704-1103, comfy_cli/command/workflow_edit.py lines 93-485, and
tests/comfy_cli/command/test_workflow_edit.py lines 145-920; commit the
resulting formatting changes and preserve Ruff’s import-sorting rules.
- Around line 715-718: Update the alias assignment in apply_specs for add_node
so a non-empty spec["as"] is rejected when the alias already exists in the
current aliases mapping, rather than overwriting the previous node ID. Raise the
established validation/error type with a clear duplicate-alias message, while
preserving normal assignment for unique aliases.
- Around line 711-732: Update the operation-dispatch loop around add_node,
connect, set_widget, and delete_node so required spec fields are validated or
accessed within error handling that preserves spec index context. Convert
missing-field KeyErrors for class_type, from, to, node, widget, and value into
ValueErrors identifying spec #{i} and the missing field, while preserving the
existing validation and unknown-operation messages.
In `@tests/comfy_cli/cql/test_object_info_env.py`:
- Around line 22-28: Format the monkeypatch.setattr call for
engine._load_from_target in the test according to Ruff’s formatter, including
the lambda and AssertionError expression, and ensure the file’s imports remain
Ruff/isort compliant. Run Ruff formatting to verify no diff remains.
---
Outside diff comments:
In `@comfy_cli/cmdline.py`:
- Around line 934-1034: Extract the non-API workflow lowering block from
validate into an _ensure_api_format helper accepting wf_data, mode, input_path,
host, port, and renderer. Keep the existing resilient_load_object_info and
convert_ui_to_api behavior, including cql_no_graph and conversion_error
reporting with typer.Exit on failure, and return the original dict unchanged
when is_api_format is true. Replace the inline block in validate with the helper
result.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 71e9d8dd-710c-4f12-9331-4dde45b613f6
⛔ Files ignored due to path filters (1)
uv.lockis excluded by!**/*.lock
📒 Files selected for processing (61)
comfy_cli/cmdline.pycomfy_cli/comfy_client.pycomfy_cli/command/assets_library.pycomfy_cli/command/cloud_http.pycomfy_cli/command/code_search.pycomfy_cli/command/generate/emit.pycomfy_cli/command/install.pycomfy_cli/command/models/models.pycomfy_cli/command/preview.pycomfy_cli/command/project.pycomfy_cli/command/run/__init__.pycomfy_cli/command/run/watcher.pycomfy_cli/command/workflow.pycomfy_cli/command/workflow_edit.pycomfy_cli/cql/engine.pycomfy_cli/cql/loader.pycomfy_cli/credentials.pycomfy_cli/discovery.pycomfy_cli/env_checker.pycomfy_cli/error_codes.pycomfy_cli/file_utils.pycomfy_cli/registry/api.pycomfy_cli/schemas/assets_library.jsoncomfy_cli/skills/__init__.pycomfy_cli/skills/comfy-director/SKILL.mdcomfy_cli/skills/comfy-fragments/SKILL.mdcomfy_cli/skills/comfy-relay/SKILL.mdcomfy_cli/skills/comfy/SKILL.mdcomfy_cli/skills/command.pycomfy_cli/standalone.pycomfy_cli/tracking.pycomfy_cli/ui.pycomfy_cli/utils.pycomfy_cli/where.pycomfy_cli/workflow_ops.pycomfy_cli/workflow_to_api.pycomfy_cli/workspace_manager.pypyproject.tomltests/comfy_cli/command/generate/fixtures/partner_nodes_object_info.jsontests/comfy_cli/command/generate/test_emit.pytests/comfy_cli/command/github/test_pr.pytests/comfy_cli/command/test_code_search.pytests/comfy_cli/command/test_run.pytests/comfy_cli/command/test_run_watcher.pytests/comfy_cli/command/test_workflow_edit.pytests/comfy_cli/command/test_workflow_edit_cloud.pytests/comfy_cli/conftest.pytests/comfy_cli/cql/test_engine.pytests/comfy_cli/cql/test_loader_resilient.pytests/comfy_cli/cql/test_loader_ttl.pytests/comfy_cli/cql/test_object_info_env.pytests/comfy_cli/skills/test_installer.pytests/comfy_cli/test_credentials.pytests/comfy_cli/test_env_checker.pytests/comfy_cli/test_standalone.pytests/comfy_cli/test_tracking.pytests/comfy_cli/test_tracking_providers.pytests/comfy_cli/test_utils.pytests/comfy_cli/test_validate_lowers_ui.pytests/comfy_cli/test_workflow_to_api.pytests/e2e/verify_tracking_live.py
💤 Files with no reviewable changes (3)
- comfy_cli/skills/comfy-fragments/SKILL.md
- comfy_cli/skills/command.py
- comfy_cli/skills/init.py
…branch Fixes the two correctness bugs and the offline-catalog/validate contract regressions surfaced by the high-effort review. Each fix carries a regression test. The two intentional features flagged (env-catalog precedence; cache-first TTL) are addressed by scoping the TTL, not removing it. Widget indexing — node-aware, not first-key (silent corruption) Dynamic-combo (COMFY_DYNAMICCOMBO_V3) widget order was expanded from the schema's FIRST key, but widgets_values is laid out by the node's SELECTED key. When the selection expands to a different sub-widget count, set-widget/slots mis-indexed every widget after the combo (e.g. set-widget <id>.seed writing into model.resolution). Add Graph.widget_order_for_node(class, widgets_values) which expands by the actual selection, and route every consumer that indexes into a real node's widgets_values through it (_widget_index + its callers, engine slots, subgraph interior read/write, recipe capture). It delegates to the static order when there's no dynamic combo or no selection. UI→API converter — don't steal the next COMBO's value (dropped widget value) The implicit seed companion heuristic consumed the next value for ANY seed-substring INT whenever that value equaled a control keyword, dropping a legitimate widget value. Peek at the next widget input: when it's a COMBO that legitimately lists the value as an option, it's that combo's value, not a phantom control_after_generate marker — keep it. validate — build the graph from the SAME env-aware catalog it lowers with validate built its Graph via Graph.load (live fetch, ignored COMFY_OBJECT_INFO_FILE) while canvas-lowering used resilient_load_object_info (which honors it). Resolve object_info ONCE through the shared loader, build the graph from it, and reuse it for lowering — consistent catalog, no double fetch. workflow edit — bad --where returns an envelope, not a traceback resolve_default(where) raises ValueError on a bad --where, but _get_graph only caught LoadError, so it escaped as a raw traceback out of every edit command. Catch it and emit the where_invalid envelope. preview — classify unknown media as unknown, not video With only imageio-ffmpeg's static ffmpeg (no ffprobe), _classify_by_ext defaulted every unrecognized extension to "video" and handed it to ffmpeg. Add a video-extension set so non-media returns "unknown" → the clean preview_unsupported_media envelope, matching the ffprobe path. object_info cache — cache-first TTL is cloud-only A cached local catalog hid a just-installed node for the whole TTL. The cloud catalog is stable and its fetch is slow (real cache win); the localhost fetch is cheap and freshness matters, so local always fetches live. The stale-cache failure fallback still applies to both. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addressed high/medium review findings (baf71ad)A high-effort multi-agent review flagged findings on this branch. The two correctness bugs and the offline-catalog/validate contract issues are fixed here, each with a regression test.
#3 ( The Low papercuts (malformed |
CI pins ruff==0.15.15, which line-wraps a few long signatures/comprehensions differently than the locally-installed 0.15.12. Purely cosmetic; no logic change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CodeQL (py/weak-sensitive-data-hashing) flags SHA-1 hashing of an id. The fork id is a deterministic derivation, not a security boundary, but SHA-256 is an equally deterministic drop-in and clears the alert. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
test_every_raised_code_is_registered failed: workflow_ops.py raises the normalized_value warning code (nearest-COMBO match in set-widget) but it was never added to error_codes.REGISTRY. This was already red on the feature commit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
comfy_cli/cql/loader.py (1)
417-435: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDocstring's "Resolution order" step 2 doesn't mention the cloud-only restriction.
The public docstring says the cache-first TTL gate applies generically, but the code right below (458-469) restricts it to
mode == "cloud". A reader skimming just the docstring (the contract most callers/maintainers will read) would miss that local always fetches live.📝 Proposed docstring tweak
- 2. Cache-first TTL gate: a per-host cache entry younger than the TTL - (default 10 minutes; ``COMFY_OBJECT_INFO_TTL`` seconds to override, - ``0`` to always fetch live) is returned with NO network call. + 2. Cache-first TTL gate (CLOUD ONLY): a per-host cache entry younger than + the TTL (default 10 minutes; ``COMFY_OBJECT_INFO_TTL`` seconds to + override, ``0`` to always fetch live) is returned with NO network + call. Local mode always fetches live so newly-installed nodes are + visible immediately.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@comfy_cli/cql/loader.py` around lines 417 - 435, The docstring for the object-info loading function should state that the cache-first TTL gate applies only when mode == "cloud"; clarify that local mode always performs a live fetch while preserving the existing cache behavior for cloud mode.comfy_cli/cmdline.py (1)
981-1009: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick winValidate explicit
--wherebefore loading
mode = wherebypasses theresolve_default()/ValueErrorhandling used incomfy_cli/command/workflow.py, so a bad--wherestill bubbles up fromresolve_target()as a raw traceback. Route this through the samewhere_invalidenvelope path to keep the typo gremlin from making a mess.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@comfy_cli/cmdline.py` around lines 981 - 1009, Validate an explicit --where value before calling resilient_load_object_info in the mode-selection block. Reuse the existing where resolution and where_invalid error-envelope behavior from workflow.py, ensuring invalid values produce the structured renderer error and exit instead of allowing resolve_target() to raise a raw traceback; preserve default resolution for omitted --where values.
♻️ Duplicate comments (1)
comfy_cli/command/workflow_edit.py (1)
520-535: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
foreachstill isn't atomic on partial failure —writtenfiles aren't surfaced.If param-set
#i(i>0) fails, workflows for#0..i-1are already on disk inout_dir, but theexceptblock still only reports the failure message, notwritten. Same gap flagged previously — a caller can't tell partial output exists without ls-ing the directory themselves. A rhyme to remember it by: fail midway, and files still stay — but no hint tells the caller today.🛡️ Proposed fix — surface partial progress on failure
except (workflow_ops.RecipeError, ValueError, KeyError) as e: - renderer.error(code="workflow_edit_invalid", message=f"foreach failed: {e}") + renderer.error( + code="workflow_edit_invalid", + message=f"foreach failed: {e}", + hint=f"{len(written)} workflow(s) were already written to {out} before the failure: {written}", + ) raise typer.Exit(code=1) from e🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@comfy_cli/command/workflow_edit.py` around lines 520 - 535, Update the foreach exception handler around written and the workflow_ops.RecipeError/ValueError/KeyError catch to include the already-written output paths in the rendered error message when partial progress exists. Preserve the existing failure exit behavior and clearly indicate that the listed files remain on disk.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@comfy_cli/command/workflow_edit.py`:
- Around line 93-98: Introduce shared Annotated option aliases near the command
definitions for the duplicated where, input, host, port, and actor options,
following the proposed WhereOpt, InputOpt, HostOpt, PortOpt, and ActorOpt
symbols. Replace the repeated declarations across add_node_cmd, set_widget_cmd,
connect_cmd, delete_cmd, capture_cmd, apply_cmd, and foreach_cmd with those
aliases, while preserving each option’s existing names, defaults, and help text;
include equivalent aliases for the remaining duplicated base-version and
stdout/in-place options.
---
Outside diff comments:
In `@comfy_cli/cmdline.py`:
- Around line 981-1009: Validate an explicit --where value before calling
resilient_load_object_info in the mode-selection block. Reuse the existing where
resolution and where_invalid error-envelope behavior from workflow.py, ensuring
invalid values produce the structured renderer error and exit instead of
allowing resolve_target() to raise a raw traceback; preserve default resolution
for omitted --where values.
In `@comfy_cli/cql/loader.py`:
- Around line 417-435: The docstring for the object-info loading function should
state that the cache-first TTL gate applies only when mode == "cloud"; clarify
that local mode always performs a live fetch while preserving the existing cache
behavior for cloud mode.
---
Duplicate comments:
In `@comfy_cli/command/workflow_edit.py`:
- Around line 520-535: Update the foreach exception handler around written and
the workflow_ops.RecipeError/ValueError/KeyError catch to include the
already-written output paths in the rendered error message when partial progress
exists. Preserve the existing failure exit behavior and clearly indicate that
the listed files remain on disk.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: b8d5ccaf-0850-4ed6-8940-e3a12592cb0e
📒 Files selected for processing (17)
comfy_cli/cmdline.pycomfy_cli/command/assets_library.pycomfy_cli/command/preview.pycomfy_cli/command/workflow.pycomfy_cli/command/workflow_edit.pycomfy_cli/cql/engine.pycomfy_cli/cql/loader.pycomfy_cli/workflow_ops.pycomfy_cli/workflow_to_api.pytests/comfy_cli/command/test_preview.pytests/comfy_cli/command/test_run.pytests/comfy_cli/command/test_workflow_edit.pytests/comfy_cli/command/test_workflow_edit_cloud.pytests/comfy_cli/cql/test_engine.pytests/comfy_cli/cql/test_loader_ttl.pytests/comfy_cli/cql/test_object_info_env.pytests/comfy_cli/test_workflow_to_api.py
Telemetry (comfy run): - Successful CLOUD submissions never emitted execution_success: the cloud branch `return`ed after execute_cloud, skipping the try's `else`. Fall through instead so cloud matches local (execution_start → execution_success). Local unaffected. - Record the RESOLVED routing target (cloud|local) on the execution events so a submission is attributable even when --where was defaulted (PostHog already tags source=cli; this adds which backend the workflow was submitted to). CodeRabbit findings: - apply_specs: reject a duplicate `as` alias instead of silently clobbering the earlier node; wrap a missing-field KeyError as "spec #i (op) is missing <field>". - foreach: surface files already written (hint + details.written) on a mid-batch failure instead of leaving the caller blind to partial output. - workflow_edit: extract the 7-way-duplicated Annotated option boilerplate (--where/--input/host/port/--actor/--base-version/--stdout) into shared aliases. - SKILL.md: fix contradictory guidance — set-widget DOES resolve subgraph values (flat 57.text / nested 57/27.text); stop sending agents to set-slot/decompose. Regression tests added for each behavioral change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rowing a bad slot A connect that targets a COMFY_AUTOGROW input by a dotted key only ever wired cleanly when that key was the exact next sequential slot (images.imageN). An index gap (images.image2), a doubled prefix (images.images.image0), or a stray element (images.foo) was silently grown into a bogus input that passed connect AND validate but failed only at submit time — the top agent workflow-edit failure class in prod. _resolve_input_target now accepts a dotted autogrow target ONLY when it names the canonical next slot; anything else raises the same workflow_edit_invalid the rest of the connect path uses, quoting the base to auto-append and the exact next free key. The bare base still auto-appends. _plan_autogrow drops its now-unused `requested` param (the caller validates before growing). Tests: TestConnect.test_autogrow_rejects_malformed_slot_targets (gap / double prefix / stray element / trailing dot -> workflow_edit_invalid, nothing minted) and test_autogrow_accepts_the_exact_next_slot_key. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@comfy_cli/workflow_ops.py`:
- Around line 1150-1152: Update the name-generation logic around the existing,
base, and name variables so it parses numeric suffixes from existing prefixed
inputs and selects the smallest unused nonnegative suffix for the canonical
base.elemN name. Ignore malformed names such as images.foo, preserve gapped
legacy names such as image0 and image2, and avoid generating a duplicate suffix.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: e00e1a78-49a3-48aa-8755-06d34be1714c
📒 Files selected for processing (2)
comfy_cli/workflow_ops.pytests/comfy_cli/command/test_workflow_edit.py
| existing = [i for i in ins if str(i.get("name", "")).startswith(base + ".")] | ||
| elem = base[:-1] if base.endswith("s") else base | ||
| name = f"{base}.{elem}{len(existing)}" |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Do not derive the next slot number from the count of prefixed inputs.
Legacy workflows can already contain malformed or gapped names from the behavior this change fixes. For example, images.image0 plus legacy images.image2 makes len(existing) == 2, so this mints a second images.image2; images.foo makes the first valid addition start at images.image1. Build the canonical images.imageN name from the first unused numeric suffix instead.
Proposed fix
- existing = [i for i in ins if str(i.get("name", "")).startswith(base + ".")]
elem = base[:-1] if base.endswith("s") else base
- name = f"{base}.{elem}{len(existing)}"
+ prefix = f"{base}.{elem}"
+ existing_names = {str(i.get("name", "")) for i in ins}
+ index = 0
+ while f"{prefix}{index}" in existing_names:
+ index += 1
+ name = f"{prefix}{index}"
return {"name": name, "type": elem_type or "*"}Based on PR objectives, invalid dotted autogrow targets were previously able to create bogus inputs.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| existing = [i for i in ins if str(i.get("name", "")).startswith(base + ".")] | |
| elem = base[:-1] if base.endswith("s") else base | |
| name = f"{base}.{elem}{len(existing)}" | |
| elem = base[:-1] if base.endswith("s") else base | |
| prefix = f"{base}.{elem}" | |
| existing_names = {str(i.get("name", "")) for i in ins} | |
| index = 0 | |
| while f"{prefix}{index}" in existing_names: | |
| index += 1 | |
| name = f"{prefix}{index}" |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@comfy_cli/workflow_ops.py` around lines 1150 - 1152, Update the
name-generation logic around the existing, base, and name variables so it parses
numeric suffixes from existing prefixed inputs and selects the smallest unused
nonnegative suffix for the canonical base.elemN name. Ignore malformed names
such as images.foo, preserve gapped legacy names such as image0 and image2, and
avoid generating a duplicate suffix.
…he base The top alpha workflow-edit failure: agents guess the classic batch-node slot shape (image0/image1) against autogrow bases whose canonical keys are images.imageN, and the generic not-found error never mentioned autogrow. A bare element name now maps onto the dotted key it implies under the same next-sequential rule: the canonical next slot grows, anything else is rejected with the base and the exact next free key.
…validate and edit UI→API lowering flattens subgraph interiors to composite ids (57:3), and `comfy validate` reported errors keyed by them — but the edit surface (slots / set-widget) speaks 57/3, so feeding a validate error's node id back into set-widget dead-ended with "node 57:3 not found in workflow". Live agent trajectories show exactly this loop (set-widget 285:288 → not found, right after validate named 285:288). Two complementary fixes, both fail-closed: - validate: when the input was a canvas graph (i.e. we lowered it and the caller never saw the flattened ids), rewrite each issue's node_id to the editable 57/3 form and keep the raw id as api_node_id for correlating with server node_errors. Already-API input passes through untouched. - set-widget: accept the colon form as an alias for the interior path (unless a literal node carries that id), so ids copied out of run/server node_errors — which stay in the flattened namespace — still resolve. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…r add-node; --at arity check Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…sign_positions Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…monotonic) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…cards
`apply_specs` threads an accumulating workflow dict through the batch, so when
spec #N fails that dict already holds the nodes added by specs #0..N-1, and
`_enrich_resolution_error` renders its "Nodes in this workflow" / "Did you
mean" inventory from it. Every caller then throws that graph away — `apply` is
atomic, `foreach` drops the failing param-set — so the ids in the hint are
fictional the moment the command returns.
The hint is phrased as an instruction ("Use an id from `comfy workflow slots`
/ `ls-nodes` — never rebuild it"), so a model reasonably treats those ids as
authoritative and addresses them next. Measured on prod comfy-agent Langfuse
traces (2026-07-27): 16/16 of every "node <id> not found in workflow" edit
failure used an id an earlier FAILED batch had advertised this way. One trace
(ff11b86931f3fbaa) burned seven consecutive `connect` calls on ids that never
existed, then had to re-read the graph and rebuild the whole batch.
Now a batch failure strips the mid-batch inventory, restates it from the
pre-batch graph, and says plainly that nothing was applied:
before: input 'image' not found on node 2453232609257464; inputs: ['images'].
Nodes in this workflow: 3 (KSampler), 7 (EmptyLatentImage),
3909203706911903 (VAEDecode), 2377033782696159 (BatchImagesNode).
Use an id from `comfy workflow slots` / `ls-nodes` — never rebuild it.
after: input 'image' not found on node 2453232609257464; inputs: ['images'].
No changes were applied — the batch was discarded. The workflow still
contains: 3 (KSampler), 7 (EmptyLatentImage). Any node id minted by
this batch is gone; re-read ids with `comfy workflow slots` /
`ls-nodes` before addressing nodes.
Tests reproduce the prod shape (two adds, then a connect into a non-existent
autogrow input), and cover the message contract, punctuation across the clause
strip, and that a successful batch is unaffected.
ComfyUI expresses a multi-type input as a COMMA-SEPARATED UNION, e.g.
`MESH,FILE_3D_GLB,FILE_3D_GLTF,...` on a 3D importer's `mesh` input. The
connect type gate compared slot types as whole strings:
if link_type and dst_type and link_type != dst_type and "*" not in (...)
so a `FILE_3D_GLB` output was refused by an input that explicitly accepts
`FILE_3D_GLB`.
Measured on prod comfy-agent traces (2026-07-23 → 07-28): ~23 connect /
apply_ops failures of exactly this shape, e.g.
type mismatch: FILE_3D_GLB output of node 4318783979958460 cannot connect to
MESH,FILE_3D_GLB,FILE_3D_GLTF,FILE_3D_OBJ,... input 'mesh' of node 2451178264782280
type mismatch: FILE_3D output of node 890530584279986 cannot connect to
FILE_3D_GLB,FILE_3D_FBX,FILE_3D_OBJ,FILE_3D_STL,FILE_3D input 'model_3d' of ...
Every one is a correct edit being refused, and the message names the source
type inside the accepted list — so the agent reads the hint, sees its own type
listed, and retries the identical call.
Compatibility is now set INTERSECTION rather than string equality. Intersection
and not substring: `FILE_3D_GL` must not satisfy an input accepting only
`FILE_3D_GLTF`/`FILE_3D_GLB`. An unknown type on either side, or a `*` wildcard,
stays permissive exactly as before, and a genuine mis-wire (IMAGE into a 3D
union) is still refused — both covered by tests.
Four independent gaps found by analysing prod comfy-agent traces
(2026-07-23 → 07-28, 4251 tool calls / 570 failures) and replaying real prod
prompts. Each is a case where the CLI held the answer and would not surface it,
or surfaced it in a form the caller could not act on.
1. add-node now fails like `nodes show` does (12 prod failures).
It raised a bare ValueError -> code=workflow_edit_invalid with the hint "run
`comfy nodes types` to list class_types". But `nodes types` lists CONNECTION
types (MODEL/LATENT/IMAGE), not class_types — so the hint was actively
misleading, and the code meant a consumer keying on `node_not_found` (as the
agent does) never saw these at all. Now raises UnknownNodeType and emits
code=node_not_found with details.close_matches, matching `nodes show`.
Two subcases get their own message because difflib is useless or misleading
for them:
- UI-only nodes (Note, MarkdownNote, PrimitiveNode, GetNode, SetNode,
Reroute) — they exist only in the editor graph. difflib returns [] for
Note/MarkdownNote and actively wrong matches for GetNode
(GeminiNode, SeedNode…).
- A subgraph INSTANCE id. `ls-nodes` prints a subgraph instance's
definition uuid as its `type`, so a caller reading ls-nodes output can
feed a uuid to add-node; there is no instantiate command, so it can never
succeed. Observed in prod: add_node '2454ad83-157c-40dd-9f19-5daaf4041ce0'.
2. ls-nodes reports bypass/mute.
ComfyUI disables a node without deleting it (mode 4 = bypass, mode 2 =
mute/never). workflow_to_api already understands both, but ls-nodes emitted
only id/type/title — so a caller could not tell a disabled node from a live
one, and would "repair" a graph that is merely bypassed, or call a workflow
runnable while a required node is muted. Emitted only when set, so a normal
node stays one clean row.
3. Output slots accept an unambiguous case/separator variant (~19 prod failures).
Callers address an output by its TYPE because no discovery surface showed the
NAME. Where name and type differ only in case or separators the intent is
unambiguous: `IMAGE` on a node whose outputs are ['image','alpha'], or
`MODEL_TASK_ID` for Tripo's 'model task_id'. Accepted ONLY when exactly one
output matches after normalizing.
Measured over the full 3573-node catalog: exactly ONE node type gains a
genuinely new ambiguity (KSampler Gradually Adding More Denoise: CONDITIONING+
/ CONDITIONING-), and the exactly-one guard keeps it failing. Twelve more have
duplicate identical output names, which exact matching already resolved to the
first — unchanged. Exact match always wins; unrelated names still fail with
the full name list.
4. `workflow slots` advertises dynamic-combo SUB-widgets (4 prod failures).
A COMFY_DYNAMICCOMBO_V3 input is one port (`model`) whose selected option
contributes widgets addressed `model.<sub>`. Those live in
widget_order_for_node but have no Port, and _node_widget_slots iterated
m.inputs — so the only place `model.prompt` ever appeared was the set-widget
error ("available: model, model.prompt, model.resolution"). 102 catalog types
carry a dynamic combo. Now driven from the widget order, with a dotted slot
inheriting its base port's type.
Deliberately NOT included: schema-driven autogrow element names. Investigating
the wire format first showed the fix is two different changes, not one — see the
PR discussion. Bundling the riskiest change with these four would have been a
mistake.
Tests: 4 files, 16 cases, each verified red before the change. Full suite 2657
passed with the 16 pre-existing failures unchanged (registry/config_parser +
onboarding, both environmental); ruff clean.
What
Consolidates the agent-workflow track into a single change on top of
main. It gives agents a structured, convergence-safe way to build and edit ComfyUI workflows, a reuse model that replaces raw fragments, an offline node catalog so edits and validation work without a live server, and the cloud plumbing to associate and run those workflows against Comfy Cloud.Why
The agent field test surfaced that raw-JSON/fragment editing was error-prone and non-convergent, and that edits/validation broke whenever no live ComfyUI server was reachable. This lands the structured-edit + offline-catalog + recipe model as one reviewable unit.
Highlights
Structured (CRDT-ready) workflow edits
workflow_ops.py: convergence-safe op model — converge-or-flag invariant and canonical-form soundness are proven in tests.workflow editcommand surface over those primitives;set-widgetresolves subgraph promoted inputs the same way it resolves slots.COMMAND_SCHEMASfor discovery.Recipes replace fragments
captureas the reuse path;foreachbulk-instantiates a recipe over N param-sets.comfy-fragmentsSKILL removed; skills docs point at recipes.Offline node catalog + validation
COMFY_OBJECT_INFO_FILEdefault offline catalog, honored in the shared CQL loader with a cache-first TTL forobject_info.validatelowers frontend/canvas graphs to API before validating;generateemits complete node inputs andvalidateflags missing required inputs.control_after_generatemarker for partner nodes.Cloud run association
comfy run --workflow-idassociates a cloud job with a workflow.COMFY_CLOUD_AUTH_TOKEN.assets library ls/ensure; shared cloud-HTTP helpers extracted tocloud_http.py.Agentic run ergonomics + perf
COMFY_NO_WATCH/--no-watch).previewrenders headless via bundled ffmpeg.Test plan
pytest --collect-only— 2749 tests, no import/collection errors.test_run_prompt,test_run_watcher,test_workflow_edit,test_workflow_edit_cloud,test_validate_lowers_ui,test_workflow_to_api,cql/test_loader_ttl,generate/test_emit.🤖 Generated with Claude Code